Compare commits

...

9 Commits

Author SHA1 Message Date
Mary-nyan
7210c17c5e misc: Enforce LF (#4253)
Because we are building everything on Windows for release at the moment,
git default line ending to CRLF causing issues when packing the
Ryujinx.sh script.

This addresses this by enforcing all files to use LF via .gitattributes.
2023-01-10 19:00:14 +01:00
Ac_K
a16854e55a ava: Cleanup AppHost (#4240)
* ava: Cleanup AppHost

This PR cleaned up the AppHost file a bit (adding the infamous extra spaces to improve readability), resorting private vars, remove useless vars, and improve the code here and there, like the AudioBackend check.

Co-Authored-By: gdkchan <5624669+gdkchan@users.noreply.github.com>

* Remove 'renderer"

* Revert currentTime

* revert if condition

Co-authored-by: gdkchan <5624669+gdkchan@users.noreply.github.com>
2023-01-10 18:45:55 +01:00
TSRBerry
3e455a90a1 Ava: Add missing null check to ContentDialogHelper.ShowAsync() (#4248)
* ava: Add missing null check to ContentDialogHelper.ShowAsync()

* Replace "is not" with != operator

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

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-01-10 09:22:25 +01:00
TSRBerry
e4413542b2 Add command line arguments to override docked mode (#4239)
* Add command line args for docked mode

* Apply suggestions from code review

Co-authored-by: Ac_K <Acoustik666@gmail.com>

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-01-10 00:59:23 +01:00
TSRBerry
8c720783f5 linux: Fix packaging step for CI & Add Ryujinx.Headless.SDL2 to Ryujinx.sh (#4249) 2023-01-09 23:45:46 +00:00
TSRBerry
8734ea9dd4 Linux: Add Avalonia detection to Ryujinx.sh (#4224)
* Revert "ava: Fix regression caused by #4013 (#4222)"

This reverts commit b9f2a96595.

* linux: Detect Ryujinx.Ava and don't rename the Ryujinx.Ava assembly
2023-01-09 22:58:51 +01:00
gdkchan
c586e6d2b7 Replace tabs with spaces across the project (#4244)
* Replace tabs with spaces across the project

* Include AXAML files too
2023-01-09 22:58:29 +01:00
TSRBerry
3a4eeb77fe headless: Change window icon size to 48x48 (#4247) 2023-01-09 18:02:41 +00:00
TSRBerry
51b3953cfc [Headless] Add missing arguments & Fix typos (#4193)
* headless: Fix typos in command line options

* Remove nullable from command line options

Add EnableMacroHLE option
Add HideCursorOnIdle option

* headless: Adjust enable-ptc help text

* headless: Use switch statement instead of if-else chain

* headless: Improve formatting for long constructors

* headless: Remove discards from SDL_ShowCursor()

* headless: Add window icon

* Fix hiding cursor on idle

At least on Wayland, SDL2 doesn't produce any mouse motion events.

* Add new command line args: BaseDataDir and UserProfile

* headless: Read icon from embedded resource

* headless: Skip SetWindowIcon() on Windows if dll isn't present

* headless: Fix division by zero

* headless: Fix command line options not working correctly

* headless: Fix crash when viewing command line options

* headless: Load window icon bmp from memory

* Add comment to the workaround for SDL_LoadBMP_RW

* headless: Enable logging to file by default

* headless: Add 3 options for --hide-cursor

Replaces --disable-hide-cursor-on-idle
2023-01-09 04:55:37 +01:00
24 changed files with 2205 additions and 2150 deletions

61
.gitattributes vendored
View File

@@ -1,63 +1,4 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
* text=auto eol=lf

View File

@@ -69,12 +69,6 @@ jobs:
- name: Publish Ryujinx.Ava
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Ava --self-contained true
if: github.event_name == 'pull_request'
- name: Rename Avalonia (Windows)
run: mv ./publish_ava/Ryujinx.Ava.exe ./publish_ava/Ryujinx.exe
if: runner.os == 'Windows' && github.event_name == 'pull_request'
- name: Rename Avalonia (Unix)
run: mv ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx
if: runner.os != 'Windows' && github.event_name == 'pull_request'
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v3
with:

View File

@@ -46,13 +46,11 @@ jobs:
shell: bash
- name: Create output dir
run: "mkdir release_output"
- name: Publish Windows
run: |
dotnet publish -c Release -r win10-x64 -o ./publish_windows/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true
dotnet publish -c Release -r win10-x64 -o ./publish_windows_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true
dotnet publish -c Release -r win10-x64 -o ./publish_windows_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true
mv ./publish_windows_ava/publish/Ryujinx.Ava.exe ./publish_windows_ava/publish/Ryujinx.exe
- name: Packing Windows builds
run: |
pushd publish_windows
@@ -73,26 +71,29 @@ jobs:
dotnet publish -c Release -r linux-x64 -o ./publish_linux/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true
dotnet publish -c Release -r linux-x64 -o ./publish_linux_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true
dotnet publish -c Release -r linux-x64 -o ./publish_linux_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true
mv ./publish_linux_ava/publish/Ryujinx.Ava ./publish_linux_ava/publish/Ryujinx
- name: Packing Linux builds
run: |
pushd publish_linux
tar --exclude "publish/Ryujinx" -cvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
tar --exclude "publish/Ryujinx" --exclude "publish/Ryujinx.sh" -cvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
python3 ../distribution/misc/add_tar_exec.py ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx" "publish/Ryujinx"
python3 ../distribution/misc/add_tar_exec.py ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.sh" "publish/Ryujinx.sh"
gzip -9 < ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
rm ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
popd
pushd publish_linux_sdl2_headless
tar --exclude "publish/Ryujinx.Headless.SDL2" -cvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
tar --exclude "publish/Ryujinx.Headless.SDL2" --exclude "publish/Ryujinx.sh" -cvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
python3 ../distribution/misc/add_tar_exec.py ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2"
python3 ../distribution/misc/add_tar_exec.py ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.sh" "publish/Ryujinx.sh"
gzip -9 < ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
rm ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
popd
pushd publish_linux_ava
tar --exclude "publish/Ryujinx" -cvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
python3 ../distribution/misc/add_tar_exec.py ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx" "publish/Ryujinx"
tar --exclude "publish/Ryujinx.Ava" --exclude "publish/Ryujinx.sh" -cvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
python3 ../distribution/misc/add_tar_exec.py ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.Ava" "publish/Ryujinx.Ava"
python3 ../distribution/misc/add_tar_exec.py ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.sh" "publish/Ryujinx.sh"
gzip -9 < ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
rm ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
popd

View File

@@ -15,6 +15,8 @@ namespace Ryujinx.Audio.Backends.CompatLayer
{
private IHardwareDeviceDriver _realDriver;
public static bool IsSupported => true;
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
{
_realDriver = realDevice;

View File

@@ -12,6 +12,8 @@ namespace Ryujinx.Audio.Backends.Dummy
private ManualResetEvent _updateRequiredEvent;
private ManualResetEvent _pauseEvent;
public static bool IsSupported => true;
public DummyHardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);

View File

@@ -26,6 +26,8 @@ namespace Ryujinx.Audio.Integration
bool SupportsSampleFormat(SampleFormat sampleFormat);
bool SupportsChannelCount(uint channelCount);
static abstract bool IsSupported { get; }
IHardwareDeviceDriver GetRealDeviceDriver()
{
return this;

View File

@@ -41,6 +41,7 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SPB.Graphics.Vulkan;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
@@ -57,42 +58,46 @@ namespace Ryujinx.Ava
{
internal class AppHost
{
private const int CursorHideIdleTime = 8; // Hide Cursor seconds
private const int CursorHideIdleTime = 8; // Hide Cursor seconds.
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
private const int TargetFps = 60;
private const float VolumeDelta = 0.05f;
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
private readonly long _ticksPerFrame;
private readonly Stopwatch _chrono;
private long _ticks;
private readonly AccountManager _accountManager;
private readonly UserChannelPersistence _userChannelPersistence;
private readonly InputManager _inputManager;
private readonly MainWindowViewModel _viewModel;
private readonly IKeyboard _keyboardInterface;
private readonly TopLevel _topLevel;
private readonly GraphicsDebugLevel _glLogLevel;
private float _newVolume;
private KeyboardHotkeyState _prevHotkeyState;
private bool _hideCursorOnIdle;
private long _lastCursorMoveTime;
private bool _isCursorInRenderer;
private bool _isStopped;
private bool _isActive;
private long _lastCursorMoveTime;
private float _newVolume;
private long _ticks = 0;
private KeyboardHotkeyState _prevHotkeyState;
private bool _renderingStarted;
private IRenderer _renderer;
private readonly Thread _renderingThread;
private bool _isMouseInRenderer;
private bool _renderingStarted;
private bool _dialogShown;
private readonly CancellationTokenSource _gpuCancellationTokenSource;
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
private readonly CancellationTokenSource _gpuCancellationTokenSource;
private bool _dialogShown;
private readonly bool _isFirmwareTitle;
private readonly object _lockObject = new();
public event EventHandler AppExit;
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
@@ -100,20 +105,15 @@ namespace Ryujinx.Ava
public RendererHost Renderer { get; }
public VirtualFileSystem VirtualFileSystem { get; }
public ContentManager ContentManager { get; }
public Switch Device { get; set; }
public NpadManager NpadManager { get; }
public TouchScreenManager TouchScreenManager { get; }
public Switch Device { get; set; }
public int Width { get; private set; }
public int Height { get; private set; }
public string ApplicationPath { get; private set; }
private bool _isFirmwareTitle;
public bool ScreenshotRequested { get; set; }
private object _lockObject = new();
private TopLevel _topLevel;
public AppHost(
RendererHost renderer,
@@ -135,7 +135,9 @@ namespace Ryujinx.Ava
_lastCursorMoveTime = Stopwatch.GetTimestamp();
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
_topLevel = topLevel;
_inputManager.SetMouseDriver(new AvaloniaMouseDriver(_topLevel, renderer));
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
NpadManager = _inputManager.CreateNpadManager();
@@ -173,14 +175,16 @@ namespace Ryujinx.Ava
if (sender is Control visual)
{
_lastCursorMoveTime = Stopwatch.GetTimestamp();
var point = e.GetCurrentPoint(visual).Position;
_isMouseInRenderer = Equals(visual.InputHitTest(point), Renderer);
_isCursorInRenderer = Equals(visual.InputHitTest(point), Renderer);
}
}
private void TopLevel_PointerLeave(object sender, PointerEventArgs e)
{
_isMouseInRenderer = false;
_isCursorInRenderer = false;
_viewModel.Cursor = Cursor.Default;
}
@@ -189,6 +193,7 @@ namespace Ryujinx.Ava
if (_renderer != null)
{
double scale = _topLevel.PlatformImpl.RenderScaling;
_renderer.Window?.SetSize((int)(size.Width * scale), (int)(size.Height * scale));
}
}
@@ -201,8 +206,9 @@ namespace Ryujinx.Ava
{
lock (_lockObject)
{
var currentTime = DateTime.Now;
string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png";
DateTime currentTime = DateTime.Now;
string filename = $"ryujinx_capture_{currentTime}-{currentTime:D2}-{currentTime:D2}_{currentTime:D2}-{currentTime:D2}-{currentTime:D2}.png";
string directory = AppDataManager.Mode switch
{
AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
@@ -266,21 +272,10 @@ namespace Ryujinx.Ava
_viewModel.IsGameRunning = true;
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName)
? string.Empty
: $" - {Device.Application.TitleName}";
string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion)
? string.Empty
: $" v{Device.Application.DisplayVersion}";
string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText)
? string.Empty
: $" ({Device.Application.TitleIdText.ToUpper()})";
string titleArchSection = Device.Application.TitleIs64Bit
? " (64-bit)"
: " (32-bit)";
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}";
string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}";
string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})";
string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
Dispatcher.UIThread.InvokeAsync(() =>
{
@@ -326,9 +321,9 @@ namespace Ryujinx.Ava
private void UpdateAudioVolumeState(object sender, ReactiveEventArgs<float> e)
{
Device?.SetVolume(e.NewValue);
Dispatcher.UIThread.Post(() =>
{
var value = e.NewValue;
_viewModel.Volume = e.NewValue;
});
}
@@ -597,19 +592,23 @@ namespace Ryujinx.Ava
internal void Resume()
{
Device?.System.TogglePauseEmulation(false);
_viewModel.IsPaused = false;
}
internal void Pause()
{
Device?.System.TogglePauseEmulation(true);
_viewModel.IsPaused = true;
}
private void InitializeSwitchInstance()
{
// Initialize KeySet.
VirtualFileSystem.ReloadKeySet();
// Initialize Renderer.
IRenderer renderer;
if (Renderer.IsVulkan)
@@ -623,12 +622,9 @@ namespace Ryujinx.Ava
renderer = new OpenGLRenderer();
}
IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver();
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
var isGALthreaded = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
if (isGALthreaded)
{
renderer = new ThreadedRenderer(renderer);
@@ -636,135 +632,16 @@ namespace Ryujinx.Ava
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALthreaded}");
if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SDL2)
{
if (SDL2HardwareDeviceDriver.IsSupported)
{
deviceDriver = new SDL2HardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL.");
if (OpenALHardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl;
MainWindowViewModel.SaveConfig();
deviceDriver = new OpenALHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SoundIO.");
if (SoundIoHardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo;
MainWindowViewModel.SaveConfig();
deviceDriver = new SoundIoHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out.");
}
}
}
}
else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo)
{
if (SoundIoHardwareDeviceDriver.IsSupported)
{
deviceDriver = new SoundIoHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, trying to fall back to SDL2.");
if (SDL2HardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2;
MainWindowViewModel.SaveConfig();
deviceDriver = new SDL2HardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL.");
if (OpenALHardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl;
MainWindowViewModel.SaveConfig();
deviceDriver = new OpenALHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, falling back to dummy audio out.");
}
}
}
}
else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.OpenAl)
{
if (OpenALHardwareDeviceDriver.IsSupported)
{
deviceDriver = new OpenALHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SDL2.");
if (SDL2HardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2;
MainWindowViewModel.SaveConfig();
deviceDriver = new SDL2HardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to SoundIO.");
if (SoundIoHardwareDeviceDriver.IsSupported)
{
Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo;
MainWindowViewModel.SaveConfig();
deviceDriver = new SoundIoHardwareDeviceDriver();
}
else
{
Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out.");
}
}
}
}
// Initialize Configuration.
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? HLE.MemoryConfiguration.MemoryConfiguration6GiB : HLE.MemoryConfiguration.MemoryConfiguration4GiB;
IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
HLE.HLEConfiguration configuration = new HLE.HLEConfiguration(VirtualFileSystem,
HLE.HLEConfiguration configuration = new(VirtualFileSystem,
_viewModel.LibHacHorizonManager,
ContentManager,
_accountManager,
_userChannelPersistence,
renderer,
deviceDriver,
InitializeAudio(),
memoryConfiguration,
_viewModel.UiHandler,
(SystemLanguage)ConfigurationState.Instance.System.Language.Value,
@@ -773,7 +650,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.System.EnableDockedMode,
ConfigurationState.Instance.System.EnablePtc,
ConfigurationState.Instance.System.EnableInternetAccess,
fsIntegrityCheckLevel,
ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
ConfigurationState.Instance.System.FsGlobalAccessLogMode,
ConfigurationState.Instance.System.SystemTimeOffset,
ConfigurationState.Instance.System.TimeZone,
@@ -785,10 +662,74 @@ namespace Ryujinx.Ava
Device = new Switch(configuration);
}
private static IHardwareDeviceDriver InitializeAudio()
{
var availableBackends = new List<AudioBackend>()
{
AudioBackend.SDL2,
AudioBackend.SoundIo,
AudioBackend.OpenAl,
AudioBackend.Dummy
};
AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value;
for (int i = 0; i < availableBackends.Count; i++)
{
if (availableBackends[i] == preferredBackend)
{
availableBackends.RemoveAt(i);
availableBackends.Insert(0, preferredBackend);
break;
}
}
static IHardwareDeviceDriver InitializeAudioBackend<T>(AudioBackend backend, AudioBackend nextBackend) where T : IHardwareDeviceDriver, new()
{
if (T.IsSupported)
{
return new T();
}
else
{
Logger.Warning?.Print(LogClass.Audio, $"{backend} is not supported, falling back to {nextBackend}.");
return null;
}
}
IHardwareDeviceDriver deviceDriver = null;
for (int i = 0; i < availableBackends.Count; i++)
{
AudioBackend currentBackend = availableBackends[i];
AudioBackend nextBackend = i + 1 < availableBackends.Count ? availableBackends[i + 1] : AudioBackend.Dummy;
deviceDriver = currentBackend switch
{
AudioBackend.SDL2 => InitializeAudioBackend<SDL2HardwareDeviceDriver>(AudioBackend.SDL2, nextBackend),
AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend),
AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend),
_ => new DummyHardwareDeviceDriver()
};
if (deviceDriver != null)
{
ConfigurationState.Instance.System.AudioBackend.Value = currentBackend;
break;
}
}
MainWindowViewModel.SaveConfig();
return deviceDriver;
}
private void Window_SizeChanged(object sender, Size e)
{
Width = (int)e.Width;
Height = (int)e.Height;
SetRendererWindowSize(e);
}
@@ -798,7 +739,7 @@ namespace Ryujinx.Ava
{
UpdateFrame();
// Polling becomes expensive if it's not slept
// Polling becomes expensive if it's not slept.
Thread.Sleep(1);
}
}
@@ -818,14 +759,7 @@ namespace Ryujinx.Ava
}
});
IRenderer renderer = Device.Gpu.Renderer;
if (renderer is ThreadedRenderer tr)
{
renderer = tr.BaseRenderer;
}
_renderer = renderer;
_renderer = Device.Gpu.Renderer is ThreadedRenderer tr ? tr.BaseRenderer : Device.Gpu.Renderer;
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
@@ -884,13 +818,12 @@ namespace Ryujinx.Ava
public void UpdateStatus()
{
// Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued
// Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued.
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
float scale = GraphicsConfig.ResScale;
if (scale != 1)
if (GraphicsConfig.ResScale != 1)
{
dockedMode += $" ({scale}x)";
dockedMode += $" ({GraphicsConfig.ResScale}x)";
}
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
@@ -907,7 +840,6 @@ namespace Ryujinx.Ava
public async Task ShowExitPrompt()
{
bool shouldExit = !ConfigurationState.Instance.ShowConfirmExit;
if (!shouldExit)
{
if (_dialogShown)
@@ -916,6 +848,7 @@ namespace Ryujinx.Ava
}
_dialogShown = true;
shouldExit = await ContentDialogHelper.CreateStopEmulationDialog();
_dialogShown = false;
@@ -933,7 +866,7 @@ namespace Ryujinx.Ava
{
Dispatcher.UIThread.Post(() =>
{
_viewModel.Cursor = _isMouseInRenderer ? InvisibleCursor : Cursor.Default;
_viewModel.Cursor = _isCursorInRenderer ? InvisibleCursor : Cursor.Default;
});
}
else
@@ -1046,7 +979,7 @@ namespace Ryujinx.Ava
}
}
// Touchscreen
// Touchscreen.
bool hasTouch = false;
if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse)

View File

@@ -6,8 +6,8 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Common.SystemInfo;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.Ui.Common;
@@ -227,6 +227,12 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
}
}
// Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
}
}
private static void PrintSystemInfo()

View File

@@ -349,7 +349,7 @@ namespace Ryujinx.Ava.UI.Helpers
Window parent = GetMainWindow();
if (parent.IsActive && parent is MainWindow window && window.ViewModel.IsGameRunning)
if (parent != null && parent.IsActive && parent is MainWindow window && window.ViewModel.IsGameRunning)
{
contentDialogOverlayWindow = new()
{

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.Headless.SDL2
{
public enum HideCursor
{
Never,
OnIdle,
Always
}
}

View File

@@ -5,7 +5,6 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Input.HLE;
using System;
using static SDL2.SDL;
namespace Ryujinx.Headless.SDL2.OpenGL
@@ -103,7 +102,13 @@ namespace Ryujinx.Headless.SDL2.OpenGL
private GraphicsDebugLevel _glLogLevel;
private SDL2OpenGLContext _openGLContext;
public OpenGLWindow(InputManager inputManager, GraphicsDebugLevel glLogLevel, AspectRatio aspectRatio, bool enableMouse) : base(inputManager, glLogLevel, aspectRatio, enableMouse)
public OpenGLWindow(
InputManager inputManager,
GraphicsDebugLevel glLogLevel,
AspectRatio aspectRatio,
bool enableMouse,
HideCursor hideCursor)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursor)
{
_glLogLevel = glLogLevel;
}

View File

@@ -6,6 +6,14 @@ namespace Ryujinx.Headless.SDL2
{
public class Options
{
// General
[Option("root-data-dir", Required = false, HelpText = "Set the custom folder path for Ryujinx data.")]
public string BaseDataDir { get; set; }
[Option("profile", Required = false, HelpText = "Set the user profile to launch the game with.")]
public string UserProfile { get; set; }
// Input
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]
@@ -23,7 +31,7 @@ namespace Ryujinx.Headless.SDL2
[Option("input-profile-5", Required = false, HelpText = "Set the input profile in use for Player 5.")]
public string InputProfile5Name { get; set; }
[Option("input-profile-6", Required = false, HelpText = "Set the input profile in use for Player 5.")]
[Option("input-profile-6", Required = false, HelpText = "Set the input profile in use for Player 6.")]
public string InputProfile6Name { get; set; }
[Option("input-profile-7", Required = false, HelpText = "Set the input profile in use for Player 7.")]
@@ -63,42 +71,45 @@ namespace Ryujinx.Headless.SDL2
public string InputIdHandheld { get; set; }
[Option("enable-keyboard", Required = false, Default = false, HelpText = "Enable or disable keyboard support (Independent from controllers binding).")]
public bool? EnableKeyboard { get; set; }
public bool EnableKeyboard { get; set; }
[Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")]
public bool? EnableMouse { get; set; }
public bool EnableMouse { get; set; }
[Option("hide-cursor", Required = false, Default = HideCursor.OnIdle, HelpText = "Change when the cursor gets hidden.")]
public HideCursor HideCursor { get; set; }
[Option("list-input-profiles", Required = false, HelpText = "List inputs profiles.")]
public bool? ListInputProfiles { get; set; }
public bool ListInputProfiles { get; set; }
[Option("list-inputs-ids", Required = false, HelpText = "List inputs ids.")]
public bool ListInputIds { get; set; }
// System
[Option("enable-ptc", Required = false, Default = true, HelpText = "Enables profiled translation cache persistency.")]
public bool? EnablePtc { get; set; }
[Option("disable-ptc", Required = false, HelpText = "Disables profiled persistent translation cache.")]
public bool DisablePtc { get; set; }
[Option("enable-internet-connection", Required = false, Default = false, HelpText = "Enables guest Internet connection.")]
public bool? EnableInternetAccess { get; set; }
public bool EnableInternetAccess { get; set; }
[Option("enable-fs-integrity-checks", Required = false, Default = true, HelpText = "Enables integrity checks on Game content files.")]
public bool? EnableFsIntegrityChecks { get; set; }
[Option("disable-fs-integrity-checks", Required = false, HelpText = "Disables integrity checks on Game content files.")]
public bool DisableFsIntegrityChecks { get; set; }
[Option("fs-global-access-log-mode", Required = false, Default = 0, HelpText = "Enables FS access log output to the console.")]
public int FsGlobalAccessLogMode { get; set; }
[Option("enable-vsync", Required = false, Default = true, HelpText = "Enables Vertical Sync.")]
public bool? EnableVsync { get; set; }
[Option("disable-vsync", Required = false, HelpText = "Disables Vertical Sync.")]
public bool DisableVsync { get; set; }
[Option("enable-shader-cache", Required = false, Default = true, HelpText = "Enables Shader cache.")]
public bool? EnableShaderCache { get; set; }
[Option("disable-shader-cache", Required = false, HelpText = "Disables Shader cache.")]
public bool DisableShaderCache { get; set; }
[Option("enable-texture-recompression", Required = false, Default = false, HelpText = "Enables Texture recompression.")]
public bool? EnableTextureRecompression { get; set; }
public bool EnableTextureRecompression { get; set; }
[Option("enable-docked-mode", Required = false, Default = true, HelpText = "Enables Docked Mode.")]
public bool? EnableDockedMode { get; set; }
[Option("disable-docked-mode", Required = false, HelpText = "Disables Docked Mode.")]
public bool DisableDockedMode { get; set; }
[Option("system-language", Required = false, Default = SystemLanguage.AmericanEnglish, HelpText = "Change System Language.")]
public SystemLanguage SystemLanguage { get; set; }
@@ -120,32 +131,32 @@ namespace Ryujinx.Headless.SDL2
// Logging
[Option("enable-file-logging", Required = false, Default = false, HelpText = "Enables logging to a file on disk.")]
public bool? EnableFileLog { get; set; }
[Option("disable-file-logging", Required = false, Default = false, HelpText = "Disables logging to a file on disk.")]
public bool DisableFileLog { get; set; }
[Option("enable-debug-logs", Required = false, Default = false, HelpText = "Enables printing debug log messages.")]
public bool? LoggingEnableDebug { get; set; }
public bool LoggingEnableDebug { get; set; }
[Option("enable-stub-logs", Required = false, Default = true, HelpText = "Enables printing stub log messages.")]
public bool? LoggingEnableStub { get; set; }
[Option("disable-stub-logs", Required = false, HelpText = "Disables printing stub log messages.")]
public bool LoggingDisableStub { get; set; }
[Option("enable-info-logs", Required = false, Default = true, HelpText = "Enables printing info log messages.")]
public bool? LoggingEnableInfo { get; set; }
[Option("disable-info-logs", Required = false, HelpText = "Disables printing info log messages.")]
public bool LoggingDisableInfo { get; set; }
[Option("enable-warning-logs", Required = false, Default = true, HelpText = "Enables printing warning log messages.")]
public bool? LoggingEnableWarning { get; set; }
[Option("disable-warning-logs", Required = false, HelpText = "Disables printing warning log messages.")]
public bool LoggingDisableWarning { get; set; }
[Option("enable-error-logs", Required = false, Default = true, HelpText = "Enables printing error log messages.")]
public bool? LoggingEnableError { get; set; }
[Option("disable-error-logs", Required = false, HelpText = "Disables printing error log messages.")]
public bool LoggingEnableError { get; set; }
[Option("enable-trace-logs", Required = false, Default = false, HelpText = "Enables printing trace log messages.")]
public bool? LoggingEnableTrace { get; set; }
public bool LoggingEnableTrace { get; set; }
[Option("enable-guest-logs", Required = false, Default = true, HelpText = "Enables printing guest log messages.")]
public bool? LoggingEnableGuest { get; set; }
[Option("disable-guest-logs", Required = false, HelpText = "Disables printing guest log messages.")]
public bool LoggingDisableGuest { get; set; }
[Option("enable-fs-access-logs", Required = false, Default = false, HelpText = "Enables printing FS access log messages.")]
public bool? LoggingEnableFsAccessLog { get; set; }
public bool LoggingEnableFsAccessLog { get; set; }
[Option("graphics-debug-level", Required = false, Default = GraphicsDebugLevel.None, HelpText = "Change Graphics API debug log level.")]
public GraphicsDebugLevel LoggingGraphicsDebugLevel { get; set; }
@@ -164,6 +175,9 @@ namespace Ryujinx.Headless.SDL2
[Option("backend-threading", Required = false, Default = BackendThreading.Auto, HelpText = "Whether or not backend threading is enabled. The \"Auto\" setting will determine whether threading should be enabled at runtime.")]
public BackendThreading BackendThreading { get; set; }
[Option("disable-macro-hle", Required= false, HelpText = "Disables high-level emulation of Macro code. Leaving this enabled improves performance but may cause graphical glitches in some games.")]
public bool DisableMacroHLE { get; set; }
[Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")]
public string GraphicsShadersDumpPath { get; set; }
@@ -176,10 +190,10 @@ namespace Ryujinx.Headless.SDL2
// Hacks
[Option("expand-ram", Required = false, Default = false, HelpText = "Expands the RAM amount on the emulated system from 4GiB to 6GiB.")]
public bool? ExpandRam { get; set; }
public bool ExpandRam { get; set; }
[Option("ignore-missing-services", Required = false, Default = false, HelpText = "Enable ignoring missing services.")]
public bool? IgnoreMissingServices { get; set; }
public bool IgnoreMissingServices { get; set; }
// Values

View File

@@ -33,7 +33,6 @@ using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
@@ -63,20 +62,6 @@ namespace Ryujinx.Headless.SDL2
Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
AppDataManager.Initialize(null);
_virtualFileSystem = VirtualFileSystem.CreateInstance();
_libHacHorizonManager = new LibHacHorizonManager();
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
_libHacHorizonManager.InitializeArpServer();
_libHacHorizonManager.InitializeBcatServer();
_libHacHorizonManager.InitializeSystemClients();
_contentManager = new ContentManager(_virtualFileSystem);
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient);
_userChannelPersistence = new UserChannelPersistence();
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
{
AutoResetEvent invoked = new AutoResetEvent(false);
@@ -97,15 +82,9 @@ namespace Ryujinx.Headless.SDL2
};
}
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
GraphicsConfig.EnableShaderCache = true;
Parser.Default.ParseArguments<Options>(args)
.WithParsed(options => Load(options))
.WithParsed(Load)
.WithNotParsed(errors => errors.Output());
_inputManager.Dispose();
}
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
@@ -343,6 +322,24 @@ namespace Ryujinx.Headless.SDL2
static void Load(Options option)
{
AppDataManager.Initialize(option.BaseDataDir);
_virtualFileSystem = VirtualFileSystem.CreateInstance();
_libHacHorizonManager = new LibHacHorizonManager();
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
_libHacHorizonManager.InitializeArpServer();
_libHacHorizonManager.InitializeBcatServer();
_libHacHorizonManager.InitializeSystemClients();
_contentManager = new ContentManager(_virtualFileSystem);
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
_userChannelPersistence = new UserChannelPersistence();
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
GraphicsConfig.EnableShaderCache = true;
IGamepad gamepad;
if (option.ListInputIds)
@@ -378,8 +375,8 @@ namespace Ryujinx.Headless.SDL2
}
_inputConfiguration = new List<InputConfig>();
_enableKeyboard = (bool)option.EnableKeyboard;
_enableMouse = (bool)option.EnableMouse;
_enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse;
void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
{
@@ -407,16 +404,16 @@ namespace Ryujinx.Headless.SDL2
}
// Setup logging level
Logger.SetEnable(LogLevel.Debug, (bool)option.LoggingEnableDebug);
Logger.SetEnable(LogLevel.Stub, (bool)option.LoggingEnableStub);
Logger.SetEnable(LogLevel.Info, (bool)option.LoggingEnableInfo);
Logger.SetEnable(LogLevel.Warning, (bool)option.LoggingEnableWarning);
Logger.SetEnable(LogLevel.Error, (bool)option.LoggingEnableError);
Logger.SetEnable(LogLevel.Trace, (bool)option.LoggingEnableTrace);
Logger.SetEnable(LogLevel.Guest, (bool)option.LoggingEnableGuest);
Logger.SetEnable(LogLevel.AccessLog, (bool)option.LoggingEnableFsAccessLog);
Logger.SetEnable(LogLevel.Debug, option.LoggingEnableDebug);
Logger.SetEnable(LogLevel.Stub, !option.LoggingDisableStub);
Logger.SetEnable(LogLevel.Info, !option.LoggingDisableInfo);
Logger.SetEnable(LogLevel.Warning, !option.LoggingDisableWarning);
Logger.SetEnable(LogLevel.Error, option.LoggingEnableError);
Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace);
Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
if ((bool)option.EnableFileLog)
if (!option.DisableFileLog)
{
Logger.AddTarget(new AsyncLogTargetWrapper(
new FileLogTarget(ReleaseInformation.GetBaseApplicationDirectory(), "file"),
@@ -426,11 +423,12 @@ namespace Ryujinx.Headless.SDL2
}
// Setup graphics configuration
GraphicsConfig.EnableShaderCache = (bool)option.EnableShaderCache;
GraphicsConfig.EnableTextureRecompression = (bool)option.EnableTextureRecompression;
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
GraphicsConfig.EnableTextureRecompression = option.EnableTextureRecompression;
GraphicsConfig.ResScale = option.ResScale;
GraphicsConfig.MaxAnisotropy = option.MaxAnisotropy;
GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath;
GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE;
while (true)
{
@@ -443,6 +441,8 @@ namespace Ryujinx.Headless.SDL2
_userChannelPersistence.ShouldRestart = false;
}
_inputManager.Dispose();
}
private static void SetupProgressHandler()
@@ -479,8 +479,8 @@ namespace Ryujinx.Headless.SDL2
private static WindowBase CreateWindow(Options options)
{
return options.GraphicsBackend == GraphicsBackend.Vulkan
? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, (bool)options.EnableMouse)
: new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, (bool)options.EnableMouse);
? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursor)
: new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursor);
}
private static IRenderer CreateRenderer(Options options, WindowBase window)
@@ -533,20 +533,20 @@ namespace Ryujinx.Headless.SDL2
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),
(bool)options.ExpandRam ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB,
options.ExpandRam ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB,
window,
options.SystemLanguage,
options.SystemRegion,
(bool)options.EnableVsync,
(bool)options.EnableDockedMode,
(bool)options.EnablePtc,
(bool)options.EnableInternetAccess,
(bool)options.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
!options.DisableVsync,
!options.DisableDockedMode,
!options.DisablePtc,
options.EnableInternetAccess,
!options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
options.FsGlobalAccessLogMode,
options.SystemTimeOffset,
options.SystemTimeZone,
options.MemoryManagerMode,
(bool)options.IgnoreMissingServices,
options.IgnoreMissingServices,
options.AspectRatio,
options.AudioVolume);
@@ -649,7 +649,7 @@ namespace Ryujinx.Headless.SDL2
}
else
{
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
Logger.Warning?.Print(LogClass.Application, $"Couldn't load '{options.InputPath}'. Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
_emulationContext.Dispose();

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
@@ -42,6 +42,16 @@
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
<Content Include="..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Ryujinx.bmp" />
</ItemGroup>
<!-- Due to .net core 3.1 embedded resource loading -->
<PropertyGroup>
<EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -10,7 +10,12 @@ namespace Ryujinx.Headless.SDL2
{
class SDL2MouseDriver : IGamepadDriver
{
private const int CursorHideIdleTime = 8; // seconds
private bool _isDisposed;
private HideCursor _hideCursor;
private bool _isHidden;
private long _lastCursorMoveTime;
public bool[] PressedButtons { get; }
@@ -18,9 +23,16 @@ namespace Ryujinx.Headless.SDL2
public Vector2 Scroll { get; private set; }
public Size _clientSize;
public SDL2MouseDriver()
public SDL2MouseDriver(HideCursor hideCursor)
{
PressedButtons = new bool[(int)MouseButton.Count];
_hideCursor = hideCursor;
if (_hideCursor == HideCursor.Always)
{
SDL_ShowCursor(SDL_DISABLE);
_isHidden = true;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -31,10 +43,53 @@ namespace Ryujinx.Headless.SDL2
return (MouseButton)(rawButton - 1);
}
public void UpdatePosition()
{
SDL_GetMouseState(out int posX, out int posY);
Vector2 position = new(posX, posY);
if (CurrentPosition != position)
{
CurrentPosition = position;
_lastCursorMoveTime = Stopwatch.GetTimestamp();
}
CheckIdle();
}
private void CheckIdle()
{
if (_hideCursor != HideCursor.OnIdle)
{
return;
}
long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime;
if (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency)
{
if (!_isHidden)
{
SDL_ShowCursor(SDL_DISABLE);
_isHidden = true;
}
}
else
{
if (_isHidden)
{
SDL_ShowCursor(SDL_ENABLE);
_isHidden = false;
}
}
}
public void Update(SDL_Event evnt)
{
if (evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN || evnt.type == SDL_EventType.SDL_MOUSEBUTTONUP)
switch (evnt.type)
{
case SDL_EventType.SDL_MOUSEBUTTONDOWN:
case SDL_EventType.SDL_MOUSEBUTTONUP:
uint rawButton = evnt.button.button;
if (rawButton > 0 && rawButton <= (int)MouseButton.Count)
@@ -43,14 +98,20 @@ namespace Ryujinx.Headless.SDL2
CurrentPosition = new Vector2(evnt.button.x, evnt.button.y);
}
}
else if (evnt.type == SDL_EventType.SDL_MOUSEMOTION)
{
break;
// NOTE: On Linux using Wayland mouse motion events won't be received at all.
case SDL_EventType.SDL_MOUSEMOTION:
CurrentPosition = new Vector2(evnt.motion.x, evnt.motion.y);
}
else if (evnt.type == SDL_EventType.SDL_MOUSEWHEEL)
{
_lastCursorMoveTime = Stopwatch.GetTimestamp();
break;
case SDL_EventType.SDL_MOUSEWHEEL:
Scroll = new Vector2(evnt.wheel.x, evnt.wheel.y);
break;
}
}

View File

@@ -12,7 +12,13 @@ namespace Ryujinx.Headless.SDL2.Vulkan
{
private GraphicsDebugLevel _glLogLevel;
public VulkanWindow(InputManager inputManager, GraphicsDebugLevel glLogLevel, AspectRatio aspectRatio, bool enableMouse) : base(inputManager, glLogLevel, aspectRatio, enableMouse)
public VulkanWindow(
InputManager inputManager,
GraphicsDebugLevel glLogLevel,
AspectRatio aspectRatio,
bool enableMouse,
HideCursor hideCursor)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursor)
{
_glLogLevel = glLogLevel;
}

View File

@@ -14,13 +14,16 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using static SDL2.SDL;
using Switch = Ryujinx.HLE.Switch;
namespace Ryujinx.Headless.SDL2
{
abstract class WindowBase : IHostUiHandler, IDisposable
abstract partial class WindowBase : IHostUiHandler, IDisposable
{
protected const int DefaultWidth = 1280;
protected const int DefaultHeight = 720;
@@ -29,6 +32,10 @@ namespace Ryujinx.Headless.SDL2
private static ConcurrentQueue<Action> MainThreadActions = new ConcurrentQueue<Action>();
[LibraryImport("SDL2")]
// TODO: Remove this as soon as SDL2-CS was updated to expose this method publicly
private static partial IntPtr SDL_LoadBMP_RW(IntPtr src, int freesrc);
public static void QueueMainThreadAction(Action action)
{
MainThreadActions.Enqueue(action);
@@ -66,9 +73,14 @@ namespace Ryujinx.Headless.SDL2
private AspectRatio _aspectRatio;
private bool _enableMouse;
public WindowBase(InputManager inputManager, GraphicsDebugLevel glLogLevel, AspectRatio aspectRatio, bool enableMouse)
public WindowBase(
InputManager inputManager,
GraphicsDebugLevel glLogLevel,
AspectRatio aspectRatio,
bool enableMouse,
HideCursor hideCursor)
{
MouseDriver = new SDL2MouseDriver();
MouseDriver = new SDL2MouseDriver(hideCursor);
_inputManager = inputManager;
_inputManager.SetMouseDriver(MouseDriver);
NpadManager = _inputManager.CreateNpadManager();
@@ -103,6 +115,34 @@ namespace Ryujinx.Headless.SDL2
TouchScreenManager.Initialize(device);
}
private void SetWindowIcon()
{
Stream iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.Headless.SDL2.Ryujinx.bmp");
byte[] iconBytes = new byte[iconStream!.Length];
if (iconStream.Read(iconBytes, 0, iconBytes.Length) != iconBytes.Length)
{
Logger.Error?.Print(LogClass.Application, "Failed to read icon to byte array.");
iconStream.Close();
return;
}
iconStream.Close();
unsafe
{
fixed (byte* iconPtr = iconBytes)
{
IntPtr rwOpsStruct = SDL_RWFromConstMem((IntPtr)iconPtr, iconBytes.Length);
IntPtr iconHandle = SDL_LoadBMP_RW(rwOpsStruct, 1);
SDL_SetWindowIcon(WindowHandle, iconHandle);
SDL_FreeSurface(iconHandle);
}
}
}
private void InitializeWindow()
{
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty
@@ -127,6 +167,8 @@ namespace Ryujinx.Headless.SDL2
throw new Exception(errorMessage);
}
SetWindowIcon();
_windowId = SDL_GetWindowID(WindowHandle);
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
@@ -146,9 +188,11 @@ namespace Ryujinx.Headless.SDL2
Renderer?.Window.SetSize(Width, Height);
MouseDriver.SetClientSize(Width, Height);
break;
case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE:
Exit();
break;
default:
break;
}
@@ -331,6 +375,9 @@ namespace Ryujinx.Headless.SDL2
Device.Hid.DebugPad.Update();
// TODO: Replace this with MouseDriver.CheckIdle() when mouse motion events are received on every supported platform.
MouseDriver.UpdatePosition();
return true;
}

View File

@@ -163,7 +163,7 @@ namespace Ryujinx.Input.HLE
ReloadConfiguration(inputConfig, enableKeyboard, enableMouse);
}
public void Update(float aspectRatio = 0)
public void Update(float aspectRatio = 1)
{
lock (_lock)
{

View File

@@ -7,6 +7,7 @@ namespace Ryujinx.Ui.Common.Helper
{
public static string[] Arguments { get; private set; }
public static bool? OverrideDockedMode { get; private set; }
public static string OverrideGraphicsBackend { get; private set; }
public static string BaseDirPathArg { get; private set; }
public static string Profile { get; private set; }
@@ -69,6 +70,12 @@ namespace Ryujinx.Ui.Common.Helper
OverrideGraphicsBackend = args[++i];
break;
case "--docked-mode":
OverrideDockedMode = true;
break;
case "--handheld-mode":
OverrideDockedMode = false;
break;
default:
LaunchPathArg = arg;
break;

View File

@@ -3,8 +3,8 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Common.SystemInfo;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.Ui;
@@ -271,6 +271,12 @@ namespace Ryujinx
}
}
// Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
}
// Logging system information.
PrintSystemInfo();

View File

@@ -1,5 +1,14 @@
#!/bin/sh
SCRIPT_DIR=$(dirname $(realpath $0))
RYUJINX_BIN="Ryujinx"
env DOTNET_EnableAlternateStackCheck=1 "$SCRIPT_DIR/Ryujinx" "$@"
if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then
RYUJINX_BIN="Ryujinx.Ava"
fi
if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
RYUJINX_BIN="Ryujinx.Headless.SDL2"
fi
env DOTNET_EnableAlternateStackCheck=1 "$SCRIPT_DIR/$RYUJINX_BIN" "$@"