Merge branch 'master' into 4304-missing-updates

This commit is contained in:
Tomas Voracek 2023-10-21 17:34:29 +02:00 committed by GitHub
commit 43a9547045
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 477 additions and 57 deletions

View file

@ -3,13 +3,13 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.0.4" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.4" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.4" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.4" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.4" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.2" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.2" />
<PackageVersion Include="Avalonia" Version="11.0.5" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.5" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.5" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.5" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.3" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.3" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
@ -35,6 +35,7 @@
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
<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="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />

View file

@ -141,3 +141,4 @@ See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.

View file

@ -682,3 +682,32 @@
END OF TERMS AND CONDITIONS
```
</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>

View file

@ -3,8 +3,8 @@ Version=1.0
Name=Ryujinx
Type=Application
Icon=Ryujinx
Exec=env DOTNET_EnableAlternateStackCheck=1 Ryujinx %f
Comment=A Nintendo Switch Emulator
Exec=Ryujinx.sh %f
Comment=Plays Nintendo Switch applications
GenericName=Nintendo Switch Emulator
Terminal=false
Categories=Game;Emulator;

View 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

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

View file

@ -72,6 +72,8 @@
"GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)",
"GameListContextMenuExtractDataLogo": "Logo",
"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",
"StatusBarSystemVersion": "System Version: {0}",
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",

View file

@ -82,4 +82,9 @@
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
</MenuItem>
<MenuItem
Click="CreateApplicationShortcut_Click"
Header="{locale:Locale GameListContextMenuCreateShortcut}"
IsEnabled="{Binding CreateShortcutEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
</MenuFlyout>

View file

@ -337,6 +337,17 @@ namespace Ryujinx.Ava.UI.Controls
}
}
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.TitleName, selectedApplication.TitleId, selectedApplication.Icon);
}
}
public async void RunApplication_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;

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

View file

@ -356,6 +356,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild();
public string LoadHeading
{
get => _loadHeading;
@ -1488,7 +1490,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Logger.RestartTime();
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path);
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path, ConfigurationState.Instance.System.Language);
PrepareLoadScreen();
@ -1696,7 +1698,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
}
#endregion
}
}

View file

@ -5,6 +5,7 @@
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
@ -460,7 +461,7 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Slider
<controls:SliderScroll
Width="130"
Maximum="1"
TickFrequency="0.01"
@ -480,7 +481,7 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Slider
<controls:SliderScroll
Width="130"
Maximum="2"
TickFrequency="0.01"
@ -604,7 +605,7 @@
<StackPanel
HorizontalAlignment="Center"
Orientation="Horizontal">
<Slider
<controls:SliderScroll
Width="130"
Maximum="1"
TickFrequency="0.01"
@ -1083,7 +1084,7 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Slider
<controls:SliderScroll
Width="130"
Maximum="1"
TickFrequency="0.01"
@ -1105,7 +1106,7 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Slider
<controls:SliderScroll
Width="130"
Maximum="2"
TickFrequency="0.01"

View file

@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
@ -23,11 +24,11 @@
Margin="0"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionGyroSensitivity}" />
<Slider
<controls:SliderScroll
Margin="0,-5,0,-5"
Width="150"
MaxWidth="150"
TickFrequency="0.01"
TickFrequency="1"
IsSnapToTickEnabled="True"
SmallChange="0.01"
Maximum="100"
@ -45,11 +46,11 @@
Margin="0"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionGyroDeadzone}" />
<Slider
<controls:SliderScroll
Margin="0,-5,0,-5"
Width="150"
MaxWidth="150"
TickFrequency="0.01"
TickFrequency="1"
IsSnapToTickEnabled="True"
SmallChange="0.01"
Maximum="100"

View file

@ -1,6 +1,7 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
@ -21,7 +22,7 @@
TextWrapping="WrapWithOverflow"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRumbleStrongMultiplier}" />
<Slider
<controls:SliderScroll
Margin="0,-5,0,-5"
Width="200"
TickFrequency="0.01"
@ -41,7 +42,7 @@
TextWrapping="WrapWithOverflow"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRumbleWeakMultiplier}" />
<Slider
<controls:SliderScroll
Margin="0,-5,0,-5"
Width="200"
MaxWidth="200"

View file

@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
@ -176,6 +177,7 @@
Content="{Binding VolumeStatusText}"
IsChecked="{Binding VolumeMuted}"
IsVisible="{Binding !ShowLoadProgress}"
PointerWheelChanged="VolumeStatus_OnPointerWheelChanged"
Background="Transparent"
BorderThickness="0"
CornerRadius="0">
@ -192,7 +194,7 @@
<ToggleSplitButton.Flyout>
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<Grid Margin="0">
<Slider
<controls:SliderScroll
MaxHeight="40"
Width="150"
Margin="0"

View file

@ -53,5 +53,20 @@ namespace Ryujinx.Ava.UI.Views.Main
{
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;
}
}
}

View file

@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
@ -50,7 +51,7 @@
VerticalAlignment="Center"
Text="{locale:Locale IconSize}"
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
<Slider
<controls:SliderScroll
Width="150"
Height="35"
Margin="5,-10,5,0"

View file

@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
@ -63,13 +64,13 @@
Maximum="100" />
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<Slider Value="{Binding Volume}"
<controls:SliderScroll Value="{Binding Volume}"
Margin="250,0,0,0"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Minimum="0"
Maximum="100"
SmallChange="5"
TickFrequency="5"
SmallChange="1"
TickFrequency="1"
IsSnapToTickEnabled="True"
LargeChange="10"
Width="350" />

View file

@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
@ -173,7 +174,7 @@
<TextBlock Text="FSR" />
</ComboBoxItem>
</ComboBox>
<Slider Value="{Binding ScalingFilterLevel}"
<controls:SliderScroll Value="{Binding ScalingFilterLevel}"
ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}"
MinWidth="150"
Margin="10,-3,0,0"

View file

@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsShaderFloat64;
public readonly bool SupportsTextureGatherOffsets;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsVertexStoreAndAtomics;
public readonly bool SupportsViewportIndexVertexTessellation;
@ -92,6 +93,7 @@ namespace Ryujinx.Graphics.GAL
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
bool supportsShaderFloat64,
bool supportsTextureGatherOffsets,
bool supportsTextureShadowLod,
bool supportsVertexStoreAndAtomics,
bool supportsViewportIndexVertexTessellation,
@ -142,6 +144,7 @@ namespace Ryujinx.Graphics.GAL
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsShaderFloat64 = supportsShaderFloat64;
SupportsTextureGatherOffsets = supportsTextureGatherOffsets;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics;
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;

View file

@ -374,6 +374,13 @@ namespace Ryujinx.Graphics.Gpu.Image
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
{
return TextureViewCompatibility.LayoutIncompatible;

View file

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

View file

@ -186,6 +186,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat;
public bool QueryHostSupportsTextureGatherOffsets() => _context.Capabilities.SupportsTextureGatherOffsets;
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
public bool QueryHostSupportsTransformFeedback() => _context.Capabilities.SupportsTransformFeedback;

View file

@ -163,6 +163,7 @@ namespace Ryujinx.Graphics.OpenGL
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
supportsShaderFloat64: true,
supportsTextureGatherOffsets: true,
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
supportsVertexStoreAndAtomics: true,
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,

View file

@ -339,6 +339,15 @@ namespace Ryujinx.Graphics.Shader
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>
/// Queries host GPU texture shadow LOD support.
/// </summary>

View file

@ -303,7 +303,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 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;
@ -402,12 +404,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
offsets[index] = offset;
}
if (!needsOffsetsEmulation)
{
hasInvalidOffset &= !areAllOffsetsConstant;
if (!hasInvalidOffset)
{
return node;
}
}
if (hasLodBias)
{
@ -434,13 +439,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
LinkedListNode<INode> oldNode = node;
if (isGather && !isShadow)
if (isGather && !isShadow && hasOffsets)
{
Operand[] newSources = new Operand[sources.Length];
sources.CopyTo(newSources, 0);
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage);
Operand[] texSizes = InsertTextureBaseSize(node, texOp, bindlessHandle, coordsCount);
int destIndex = 0;
@ -455,7 +460,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
Operand offset = Local();
Operand intOffset = offsets[index + (hasOffsets ? compIndex * coordsCount : 0)];
Operand intOffset = offsets[index + compIndex * coordsCount];
node.List.AddBefore(node, new Operation(
Instruction.FP32 | Instruction.Divide,
@ -478,7 +483,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Format,
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
texOp.Binding,
1,
1 << 3, // W component: i=0, j=0
new[] { dests[destIndex++] },
newSources);
@ -502,7 +507,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
}
else
{
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage);
Operand[] texSizes = isGather
? InsertTextureBaseSize(node, texOp, bindlessHandle, coordsCount)
: InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage);
for (int index = 0; index < coordsCount; index++)
{
@ -549,6 +556,43 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node;
}
private static Operand[] InsertTextureBaseSize(
LinkedListNode<INode> node,
TextureOperation texOp,
Operand bindlessHandle,
int coordsCount)
{
Operand[] texSizes = new Operand[coordsCount];
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,

View file

@ -605,6 +605,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsShaderBallot: false,
supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
supportsShaderFloat64: Capabilities.SupportsShaderFloat64,
supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended && !IsMoltenVk,
supportsTextureShadowLod: false,
supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,

View file

@ -546,7 +546,7 @@ namespace Ryujinx.Ui.App.Common
return appMetadata;
}
public byte[] GetApplicationIcon(string applicationPath)
public byte[] GetApplicationIcon(string applicationPath, Language desiredTitleLanguage)
{
byte[] applicationIcon = null;
@ -600,7 +600,7 @@ namespace Ryujinx.Ui.App.Common
{
using var icon = new UniqueRef<IFile>();
controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
controlFs.OpenFile(ref icon.Ref, $"/icon_{desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
using MemoryStream stream = new();

View file

@ -0,0 +1,171 @@
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using ShellLink;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.Versioning;
using Image = System.Drawing.Image;
namespace Ryujinx.Ui.Common.Helper
{
public static class ShortcutHelper
{
[SupportedOSPlatform("windows")]
private static void CreateShortcutWindows(string applicationFilePath, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
{
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
iconPath += ".ico";
MemoryStream iconDataStream = new(iconData);
using Image image = Image.FromStream(iconDataStream);
using Bitmap bitmap = new(128, 128);
using System.Drawing.Graphics graphic = System.Drawing.Graphics.FromImage(bitmap);
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.DrawImage(image, 0, 0, 128, 128);
SaveBitmapAsIcon(bitmap, iconPath);
var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(basePath, applicationFilePath), iconPath, 0);
shortcut.StringData.NameString = cleanedAppName;
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
}
[SupportedOSPlatform("linux")]
private static void CreateShortcutLinux(string applicationFilePath, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
{
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.Ui.Common/shortcut-template.desktop");
iconPath += ".png";
var image = SixLabors.ImageSharp.Image.Load<Rgba32>(iconData);
image.SaveAsPng(iconPath);
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
outputFile.Write(desktopFile, cleanedAppName, iconPath, GetArgsString(basePath, applicationFilePath));
}
[SupportedOSPlatform("macos")]
private static void CreateShortcutMacos(string appFilePath, byte[] iconData, string desktopPath, string cleanedAppName)
{
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName);
var plistFile = EmbeddedResources.ReadAllText("Ryujinx.Ui.Common/shortcut-template.plist");
// Macos .App folder
string contentFolderPath = Path.Combine(desktopPath, cleanedAppName + ".app", "Contents");
string scriptFolderPath = Path.Combine(contentFolderPath, "MacOS");
if (!Directory.Exists(scriptFolderPath))
{
Directory.CreateDirectory(scriptFolderPath);
}
// Runner script
const string ScriptName = "runner.sh";
string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
using StreamWriter scriptFile = new(scriptPath);
scriptFile.WriteLine("#!/bin/sh");
scriptFile.WriteLine(GetArgsString(basePath, appFilePath));
// Set execute permission
FileInfo fileInfo = new(scriptPath);
fileInfo.UnixFileMode |= UnixFileMode.UserExecute;
// img
string resourceFolderPath = Path.Combine(contentFolderPath, "Resources");
if (!Directory.Exists(resourceFolderPath))
{
Directory.CreateDirectory(resourceFolderPath);
}
const string IconName = "icon.png";
var image = SixLabors.ImageSharp.Image.Load<Rgba32>(iconData);
image.SaveAsPng(Path.Combine(resourceFolderPath, IconName));
// plist file
using StreamWriter outputFile = new(Path.Combine(contentFolderPath, "Info.plist"));
outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
}
public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData)
{
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
if (OperatingSystem.IsWindows())
{
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
CreateShortcutWindows(applicationFilePath, iconData, iconPath, cleanedAppName, desktopPath);
return;
}
if (OperatingSystem.IsLinux())
{
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
Directory.CreateDirectory(iconPath);
CreateShortcutLinux(applicationFilePath, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
return;
}
if (OperatingSystem.IsMacOS())
{
CreateShortcutMacos(applicationFilePath, iconData, desktopPath, cleanedAppName);
return;
}
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
}
private static string GetArgsString(string basePath, string appFilePath)
{
// args are first defined as a list, for easier adjustments in the future
var argsList = new List<string>
{
basePath,
};
if (!string.IsNullOrEmpty(CommandLineState.BaseDirPathArg))
{
argsList.Add("--root-data-dir");
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
}
argsList.Add($"\"{appFilePath}\"");
return String.Join(" ", argsList);
}
/// <summary>
/// Creates a Icon (.ico) file using the source bitmap image at the specified file path.
/// </summary>
/// <param name="source">The source bitmap image that will be saved as an .ico file</param>
/// <param name="filePath">The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).</param>
[SupportedOSPlatform("windows")]
private static void SaveBitmapAsIcon(Bitmap source, string filePath)
{
// Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz
byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 };
using FileStream fs = new(filePath, FileMode.Create);
fs.Write(header);
// Writing actual data
source.Save(fs, ImageFormat.Png);
// Getting data length (file length minus header)
long dataLength = fs.Length - header.Length;
// Write it in the correct place
fs.Seek(14, SeekOrigin.Begin);
fs.WriteByte((byte)dataLength);
fs.WriteByte((byte)(dataLength >> 8));
}
}
}

View file

@ -45,8 +45,18 @@
<EmbeddedResource Include="Resources\Logo_Twitter_Light.png" />
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == ''">
<EmbeddedResource Include="..\..\distribution\linux\shortcut-template.desktop" />
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64' OR '$(RuntimeIdentifier)' == 'osx-arm64' OR '$(RuntimeIdentifier)' == ''">
<EmbeddedResource Include="..\..\distribution\macos\shortcut-template.plist" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DiscordRichPresence" />
<PackageReference Include="securifybv.ShellLink" />
<PackageReference Include="System.Drawing.Common" />
</ItemGroup>
<ItemGroup>

View file

@ -23,6 +23,7 @@ namespace Ryujinx.Ui.Widgets
private MenuItem _purgeShaderCacheMenuItem;
private MenuItem _openPtcDirMenuItem;
private MenuItem _openShaderCacheDirMenuItem;
private MenuItem _createShortcutMenuItem;
private void InitializeComponent()
{
@ -187,6 +188,15 @@ namespace Ryujinx.Ui.Widgets
};
_openShaderCacheDirMenuItem.Activated += OpenShaderCacheDir_Clicked;
//
// _createShortcutMenuItem
//
_createShortcutMenuItem = new MenuItem("Create Application Shortcut")
{
TooltipText = "Create a Desktop Shortcut that launches the selected Application."
};
_createShortcutMenuItem.Activated += CreateShortcut_Clicked;
ShowComponent();
}
@ -213,6 +223,7 @@ namespace Ryujinx.Ui.Widgets
Add(new SeparatorMenuItem());
Add(_manageCacheMenuItem);
Add(_extractMenuItem);
Add(_createShortcutMenuItem);
ShowAll();
}

View file

@ -10,6 +10,7 @@ using LibHac.Ns;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
@ -77,6 +78,8 @@ namespace Ryujinx.Ui.Widgets
_extractExeFsMenuItem.Sensitive = hasNca;
_extractLogoMenuItem.Sensitive = hasNca;
_createShortcutMenuItem.Sensitive = !ReleaseInformation.IsFlatHubBuild();
PopupAtPointer(null);
}
@ -629,5 +632,11 @@ namespace Ryujinx.Ui.Widgets
}
}
}
private void CreateShortcut_Clicked(object sender, EventArgs args)
{
byte[] appIcon = new ApplicationLibrary(_virtualFileSystem).GetApplicationIcon(_titleFilePath, ConfigurationState.Instance.System.Language);
ShortcutHelper.CreateAppShortcut(_titleFilePath, _titleName, _titleIdText, appIcon);
}
}
}