Add Support for Post Processing Effects (#3616)

* Add Post Processing Effects

* fix events and shader issues

* fix gtk upscale slider value

* fix bgra games

* don't swap swizzle if already swapped

* restore opengl texture state after effects run

* addressed review

* use single pipeline for smaa and fsr

* call finish on all pipelines

* addressed review

* attempt fix file case

* attempt fixing file case

* fix filter level tick frequency

* adjust filter slider margins

* replace fxaa shaders with original shader

* addressed review
This commit is contained in:
Emmanuel Hansen 2023-02-27 21:11:55 +00:00 committed by GitHub
parent 5d85468302
commit 80b4972139
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 21954 additions and 26 deletions

View File

@ -171,6 +171,11 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAntiAliasing;
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
_gpuCancellationTokenSource = new CancellationTokenSource(); _gpuCancellationTokenSource = new CancellationTokenSource();
} }
@ -193,6 +198,17 @@ namespace Ryujinx.Ava
} }
} }
} }
private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e)
{
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
}
private void UpdateScalingFilter(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.ScalingFilter> e)
{
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
}
private void ShowCursor() private void ShowCursor()
{ {
@ -345,6 +361,11 @@ namespace Ryujinx.Ava
} }
} }
private void UpdateAntiAliasing(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.AntiAliasing> e)
{
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue);
}
private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e) private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e)
{ {
Device?.System.ChangeDockedModeState(e.NewValue); Device?.System.ChangeDockedModeState(e.NewValue);
@ -411,6 +432,9 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState; ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState; ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState; ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter;
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel;
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing;
_topLevel.PointerMoved -= TopLevel_PointerMoved; _topLevel.PointerMoved -= TopLevel_PointerMoved;
@ -788,6 +812,10 @@ namespace Ryujinx.Ava
Device.Gpu.Renderer.Initialize(_glLogLevel); Device.Gpu.Renderer.Initialize(_glLogLevel);
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value);
_renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
_renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
Width = (int)_rendererHost.Bounds.Width; Width = (int)_rendererHost.Bounds.Width;
Height = (int)_rendererHost.Bounds.Height; Height = (int)_rendererHost.Bounds.Height;

View File

@ -626,6 +626,16 @@
"Recover": "Recover", "Recover": "Recover",
"UserProfilesRecoverHeading" : "Saves were found for the following accounts", "UserProfilesRecoverHeading" : "Saves were found for the following accounts",
"UserProfilesRecoverEmptyList": "No profiles to recover", "UserProfilesRecoverEmptyList": "No profiles to recover",
"GraphicsAATooltip": "Applies anti-aliasing to the game render",
"GraphicsAALabel": "Anti-Aliasing:",
"GraphicsScalingFilterLabel": "Scaling Filter:",
"GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling",
"GraphicsScalingFilterLevelLabel": "Level",
"GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level",
"SmaaLow": "SMAA Low",
"SmaaMedium": "SMAA Medium",
"SmaaHigh": "SMAA High",
"SmaaUltra": "SMAA Ultra",
"UserEditorTitle" : "Edit User", "UserEditorTitle" : "Edit User",
"UserEditorTitleCreate" : "Create User" "UserEditorTitleCreate" : "Create User"
} }

View File

@ -45,6 +45,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private KeyboardHotkeys _keyboardHotkeys; private KeyboardHotkeys _keyboardHotkeys;
private int _graphicsBackendIndex; private int _graphicsBackendIndex;
private string _customThemePath; private string _customThemePath;
private int _scalingFilter;
private int _scalingFilterLevel;
public event Action CloseWindow; public event Action CloseWindow;
public event Action SaveSettingsEvent; public event Action SaveSettingsEvent;
@ -153,6 +155,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsSDL2Enabled { get; set; } public bool IsSDL2Enabled { get; set; }
public bool EnableCustomTheme { get; set; } public bool EnableCustomTheme { get; set; }
public bool IsCustomResolutionScaleActive => _resolutionScale == 4; public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
public bool IsVulkanSelected => GraphicsBackendIndex == 0; public bool IsVulkanSelected => GraphicsBackendIndex == 0;
public bool UseHypervisor { get; set; } public bool UseHypervisor { get; set; }
@ -179,6 +183,18 @@ namespace Ryujinx.Ava.UI.ViewModels
public int AudioBackend { get; set; } public int AudioBackend { get; set; }
public int MaxAnisotropy { get; set; } public int MaxAnisotropy { get; set; }
public int AspectRatio { get; set; } public int AspectRatio { get; set; }
public int AntiAliasingEffect { get; set; }
public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0");
public int ScalingFilterLevel
{
get => _scalingFilterLevel;
set
{
_scalingFilterLevel = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ScalingFilterLevelText));
}
}
public int OpenglDebugLevel { get; set; } public int OpenglDebugLevel { get; set; }
public int MemoryMode { get; set; } public int MemoryMode { get; set; }
public int BaseStyleIndex { get; set; } public int BaseStyleIndex { get; set; }
@ -192,6 +208,16 @@ namespace Ryujinx.Ava.UI.ViewModels
OnPropertyChanged(nameof(IsVulkanSelected)); OnPropertyChanged(nameof(IsVulkanSelected));
} }
} }
public int ScalingFilter
{
get => _scalingFilter;
set
{
_scalingFilter = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsScalingFilterActive));
}
}
public int PreferredGpuIndex { get; set; } public int PreferredGpuIndex { get; set; }
@ -365,6 +391,9 @@ namespace Ryujinx.Ava.UI.ViewModels
AspectRatio = (int)config.Graphics.AspectRatio.Value; AspectRatio = (int)config.Graphics.AspectRatio.Value;
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value; GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
ShaderDumpPath = config.Graphics.ShadersDumpPath; ShaderDumpPath = config.Graphics.ShadersDumpPath;
AntiAliasingEffect = (int)config.Graphics.AntiAliasing.Value;
ScalingFilter = (int)config.Graphics.ScalingFilter.Value;
ScalingFilterLevel = config.Graphics.ScalingFilterLevel.Value;
// Audio // Audio
AudioBackend = (int)config.System.AudioBackend.Value; AudioBackend = (int)config.System.AudioBackend.Value;
@ -447,6 +476,9 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Graphics.ResScaleCustom.Value = CustomResolutionScale; config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy); config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio; config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
config.Graphics.AntiAliasing.Value = (AntiAliasing)AntiAliasingEffect;
config.Graphics.ScalingFilter.Value = (ScalingFilter)ScalingFilter;
config.Graphics.ScalingFilterLevel.Value = ScalingFilterLevel;
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex) if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
{ {

View File

@ -7,6 +7,7 @@
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"
Design.Width="1000"
mc:Ignorable="d" mc:Ignorable="d"
x:CompileBindings="True" x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel"> x:DataType="viewModels:SettingsViewModel">
@ -111,6 +112,83 @@
Minimum="0.1" Minimum="0.1"
Value="{Binding CustomResolutionScale}" /> Value="{Binding CustomResolutionScale}" />
</StackPanel> </StackPanel>
<StackPanel
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale GraphicsAATooltip}"
Text="{locale:Locale GraphicsAALabel}"
Width="250" />
<ComboBox Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale GraphicsAATooltip}"
SelectedIndex="{Binding AntiAliasingEffect}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="FXAA" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SmaaLow}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SmaaMedium}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SmaaHigh}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SmaaUltra}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
<StackPanel
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}"
Text="{locale:Locale GraphicsScalingFilterLabel}"
Width="250" />
<ComboBox Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}"
SelectedIndex="{Binding ScalingFilter}">
<ComboBoxItem>
<TextBlock Text="Bilinear" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="Nearest" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="FSR" />
</ComboBoxItem>
</ComboBox>
<Slider Value="{Binding ScalingFilterLevel}"
ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}"
MinWidth="150"
Margin="10,-3,0,0"
Height="32"
Padding="0,-5"
IsVisible="{Binding IsScalingFilterActive}"
TickFrequency="1"
IsSnapToTickEnabled="True"
LargeChange="10"
SmallChange="1"
VerticalAlignment="Center"
Minimum="0"
Maximum="100" />
<TextBlock Margin="5,0"
Width="40"
IsVisible="{Binding IsScalingFilterActive}"
Text="{Binding ScalingFilterLevelText}"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" <TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale AnisotropyTooltip}" ToolTip.Tip="{locale:Locale AnisotropyTooltip}"

View File

@ -0,0 +1,12 @@
namespace Ryujinx.Common.Configuration
{
public enum AntiAliasing
{
None,
Fxaa,
SmaaLow,
SmaaMedium,
SmaaHigh,
SmaaUltra
}
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.Common.Configuration
{
public enum ScalingFilter
{
Bilinear,
Nearest,
Fsr
}
}

View File

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.GAL
{
public enum AntiAliasing
{
None,
Fxaa,
SmaaLow,
SmaaMedium,
SmaaHigh,
SmaaUltra
}
}

View File

@ -9,5 +9,9 @@ namespace Ryujinx.Graphics.GAL
void SetSize(int width, int height); void SetSize(int width, int height);
void ChangeVSyncMode(bool vsyncEnabled); void ChangeVSyncMode(bool vsyncEnabled);
void SetAntiAliasing(AntiAliasing antialiasing);
void SetScalingFilter(ScalingFilter type);
void SetScalingFilterLevel(float level);
} }
} }

View File

@ -32,5 +32,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
} }
public void ChangeVSyncMode(bool vsyncEnabled) { } public void ChangeVSyncMode(bool vsyncEnabled) { }
public void SetAntiAliasing(AntiAliasing effect) { }
public void SetScalingFilter(ScalingFilter type) { }
public void SetScalingFilterLevel(float level) { }
} }
} }

View File

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.GAL
{
public enum ScalingFilter
{
Bilinear,
Nearest,
Fsr
}
}

View File

@ -0,0 +1,177 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
using static Ryujinx.Graphics.OpenGL.Effects.ShaderHelper;
namespace Ryujinx.Graphics.OpenGL.Effects
{
internal class FsrScalingFilter : IScalingFilter
{
private readonly OpenGLRenderer _renderer;
private int _inputUniform;
private int _outputUniform;
private int _sharpeningUniform;
private int _srcX0Uniform;
private int _srcX1Uniform;
private int _srcY0Uniform;
private int _scalingShaderProgram;
private int _sharpeningShaderProgram;
private float _scale = 1;
private int _srcY1Uniform;
private int _dstX0Uniform;
private int _dstX1Uniform;
private int _dstY0Uniform;
private int _dstY1Uniform;
private int _scaleXUniform;
private int _scaleYUniform;
private TextureStorage _intermediaryTexture;
public float Level
{
get => _scale;
set
{
_scale = MathF.Max(0.01f, value);
}
}
public FsrScalingFilter(OpenGLRenderer renderer, IPostProcessingEffect filter)
{
Initialize();
_renderer = renderer;
}
public void Dispose()
{
if (_scalingShaderProgram != 0)
{
GL.DeleteProgram(_scalingShaderProgram);
GL.DeleteProgram(_sharpeningShaderProgram);
}
_intermediaryTexture?.Dispose();
}
private void Initialize()
{
var scalingShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl");
var sharpeningShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_sharpening.glsl");
var fsrA = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_a.h");
var fsr1 = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_fsr1.h");
scalingShader = scalingShader.Replace("#include \"ffx_a.h\"", fsrA);
scalingShader = scalingShader.Replace("#include \"ffx_fsr1.h\"", fsr1);
sharpeningShader = sharpeningShader.Replace("#include \"ffx_a.h\"", fsrA);
sharpeningShader = sharpeningShader.Replace("#include \"ffx_fsr1.h\"", fsr1);
_scalingShaderProgram = CompileProgram(scalingShader, ShaderType.ComputeShader);
_sharpeningShaderProgram = CompileProgram(sharpeningShader, ShaderType.ComputeShader);
_inputUniform = GL.GetUniformLocation(_scalingShaderProgram, "Source");
_outputUniform = GL.GetUniformLocation(_scalingShaderProgram, "imgOutput");
_sharpeningUniform = GL.GetUniformLocation(_sharpeningShaderProgram, "sharpening");
_srcX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX0");
_srcX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX1");
_srcY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY0");
_srcY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY1");
_dstX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX0");
_dstX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX1");
_dstY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY0");
_dstY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY1");
_scaleXUniform = GL.GetUniformLocation(_scalingShaderProgram, "scaleX");
_scaleYUniform = GL.GetUniformLocation(_scalingShaderProgram, "scaleY");
}
public void Run(
TextureView view,
TextureView destinationTexture,
int width,
int height,
Extents2D source,
Extents2D destination)
{
if (_intermediaryTexture == null || _intermediaryTexture.Info.Width != width || _intermediaryTexture.Info.Height != height)
{
_intermediaryTexture?.Dispose();
var originalInfo = view.Info;
var info = new TextureCreateInfo(width,
height,
originalInfo.Depth,
originalInfo.Levels,
originalInfo.Samples,
originalInfo.BlockWidth,
originalInfo.BlockHeight,
originalInfo.BytesPerPixel,
originalInfo.Format,
originalInfo.DepthStencilMode,
originalInfo.Target,
originalInfo.SwizzleR,
originalInfo.SwizzleG,
originalInfo.SwizzleB,
originalInfo.SwizzleA);
_intermediaryTexture = new TextureStorage(_renderer, info, view.ScaleFactor);
_intermediaryTexture.CreateDefaultView();
}
var textureView = _intermediaryTexture.CreateView(_intermediaryTexture.Info, 0, 0) as TextureView;
int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
GL.ActiveTexture(TextureUnit.Texture0);
int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D);
GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
int threadGroupWorkRegionDim = 16;
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
// Scaling pass
float srcWidth = Math.Abs(source.X2 - source.X1);
float srcHeight = Math.Abs(source.Y2 - source.Y1);
float scaleX = srcWidth / view.Width;
float scaleY = srcHeight / view.Height;
GL.UseProgram(_scalingShaderProgram);
view.Bind(0);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform1(_srcX0Uniform, (float)source.X1);
GL.Uniform1(_srcX1Uniform, (float)source.X2);
GL.Uniform1(_srcY0Uniform, (float)source.Y1);
GL.Uniform1(_srcY1Uniform, (float)source.Y2);
GL.Uniform1(_dstX0Uniform, (float)destination.X1);
GL.Uniform1(_dstX1Uniform, (float)destination.X2);
GL.Uniform1(_dstY0Uniform, (float)destination.Y1);
GL.Uniform1(_dstY1Uniform, (float)destination.Y2);
GL.Uniform1(_scaleXUniform, scaleX);
GL.Uniform1(_scaleYUniform, scaleY);
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
// Sharpening Pass
GL.UseProgram(_sharpeningShaderProgram);
GL.BindImageTexture(0, destinationTexture.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
textureView.Bind(0);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform1(_sharpeningUniform, 1.5f - (Level * 0.01f * 1.5f));
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.UseProgram(previousProgram);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
(_renderer.Pipeline as Pipeline).RestoreImages1And2();
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding);
GL.ActiveTexture((TextureUnit)previousUnit);
}
}
}

View File

@ -0,0 +1,81 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
using Ryujinx.Graphics.OpenGL.Image;
namespace Ryujinx.Graphics.OpenGL.Effects
{
internal class FxaaPostProcessingEffect : IPostProcessingEffect
{
private readonly OpenGLRenderer _renderer;
private int _resolutionUniform;
private int _inputUniform;
private int _outputUniform;
private int _shaderProgram;
private TextureStorage _textureStorage;
public FxaaPostProcessingEffect(OpenGLRenderer renderer)
{
Initialize();
_renderer = renderer;
}
public void Dispose()
{
if (_shaderProgram != 0)
{
GL.DeleteProgram(_shaderProgram);
_textureStorage?.Dispose();
}
}
private void Initialize()
{
_shaderProgram = ShaderHelper.CompileProgram(EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fxaa.glsl"), ShaderType.ComputeShader);
_resolutionUniform = GL.GetUniformLocation(_shaderProgram, "invResolution");
_inputUniform = GL.GetUniformLocation(_shaderProgram, "inputTexture");
_outputUniform = GL.GetUniformLocation(_shaderProgram, "imgOutput");
}
public TextureView Run(TextureView view, int width, int height)
{
if (_textureStorage == null || _textureStorage.Info.Width != view.Width || _textureStorage.Info.Height != view.Height)
{
_textureStorage?.Dispose();
_textureStorage = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
_textureStorage.CreateDefaultView();
}
var textureView = _textureStorage.CreateView(view.Info, 0, 0) as TextureView;
int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
GL.ActiveTexture(TextureUnit.Texture0);
int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D);
GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
GL.UseProgram(_shaderProgram);
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
view.Bind(0);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.UseProgram(previousProgram);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
(_renderer.Pipeline as Pipeline).RestoreImages1And2();
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding);
GL.ActiveTexture((TextureUnit)previousUnit);
return textureView;
}
}
}

View File

@ -0,0 +1,11 @@
using Ryujinx.Graphics.OpenGL.Image;
using System;
namespace Ryujinx.Graphics.OpenGL.Effects
{
internal interface IPostProcessingEffect : IDisposable
{
const int LocalGroupSize = 64;
TextureView Run(TextureView view, int width, int height);
}
}

View File

@ -0,0 +1,18 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
namespace Ryujinx.Graphics.OpenGL.Effects
{
internal interface IScalingFilter : IDisposable
{
float Level { get; set; }
void Run(
TextureView view,
TextureView destinationTexture,
int width,
int height,
Extents2D source,
Extents2D destination);
}
}

View File

@ -0,0 +1,40 @@
using OpenTK.Graphics.OpenGL;
using System;
namespace Ryujinx.Graphics.OpenGL.Effects
{
internal static class ShaderHelper
{
public static int CompileProgram(string shaderCode, ShaderType shaderType)
{
var shader = GL.CreateShader(shaderType);
GL.ShaderSource(shader, shaderCode);
GL.CompileShader(shader);
var program = GL.CreateProgram();
GL.AttachShader(program, shader);
GL.LinkProgram(program);
GL.DetachShader(program, shader);
GL.DeleteShader(shader);
return program;
}
public static int CompileProgram(string[] shaders, ShaderType shaderType)
{
var shader = GL.CreateShader(shaderType);
GL.ShaderSource(shader, shaders.Length, shaders, (int[])null);
GL.CompileShader(shader);
var program = GL.CreateProgram();
GL.AttachShader(program, shader);
GL.LinkProgram(program);
GL.DetachShader(program, shader);
GL.DeleteShader(shader);
return program;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,88 @@
#version 430 core
precision mediump float;
layout (local_size_x = 64) in;
layout(rgba8, binding = 0, location=0) uniform image2D imgOutput;
layout( location=1 ) uniform sampler2D Source;
layout( location=2 ) uniform float srcX0;
layout( location=3 ) uniform float srcX1;
layout( location=4 ) uniform float srcY0;
layout( location=5 ) uniform float srcY1;
layout( location=6 ) uniform float dstX0;
layout( location=7 ) uniform float dstX1;
layout( location=8 ) uniform float dstY0;
layout( location=9 ) uniform float dstY1;
layout( location=10 ) uniform float scaleX;
layout( location=11 ) uniform float scaleY;
#define A_GPU 1
#define A_GLSL 1
#include "ffx_a.h"
#define FSR_EASU_F 1
AU4 con0, con1, con2, con3;
float srcW, srcH, dstW, dstH;
vec2 bLeft, tRight;
AF2 translate(AF2 pos) {
return AF2(pos.x * scaleX, pos.y * scaleY);
}
void setBounds(vec2 bottomLeft, vec2 topRight) {
bLeft = bottomLeft;
tRight = topRight;
}
AF2 translateDest(AF2 pos) {
AF2 translatedPos = AF2(pos.x, pos.y);
translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
translatedPos.y = dstY0 > dstY1 ? dstY0 + dstY1 - translatedPos.y - 1: translatedPos.y;
return translatedPos;
}
AF4 FsrEasuRF(AF2 p) { AF4 res = textureGather(Source, translate(p), 0); return res; }
AF4 FsrEasuGF(AF2 p) { AF4 res = textureGather(Source, translate(p), 1); return res; }
AF4 FsrEasuBF(AF2 p) { AF4 res = textureGather(Source, translate(p), 2); return res; }
#include "ffx_fsr1.h"
float insideBox(vec2 v) {
vec2 s = step(bLeft, v) - step(tRight, v);
return s.x * s.y;
}
void CurrFilter(AU2 pos)
{
if((insideBox(vec2(pos.x, pos.y))) == 0) {
imageStore(imgOutput, ASU2(pos.x, pos.y), AF4(0,0,0,1));
return;
}
AF3 c;
FsrEasuF(c, AU2(pos.x - bLeft.x, pos.y - bLeft.y), con0, con1, con2, con3);
imageStore(imgOutput, ASU2(translateDest(pos)), AF4(c, 1));
}
void main() {
srcW = abs(srcX1 - srcX0);
srcH = abs(srcY1 - srcY0);
dstW = abs(dstX1 - dstX0);
dstH = abs(dstY1 - dstY0);
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
setBounds(vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1),
vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0));
// Upscaling
FsrEasuCon(con0, con1, con2, con3,
srcW, srcH, // Viewport size (top left aligned) in the input image which is to be scaled.
srcW, srcH, // The size of the input image.
dstW, dstH); // The output resolution.
CurrFilter(gxy);
gxy.x += 8u;
CurrFilter(gxy);
gxy.y += 8u;
CurrFilter(gxy);
gxy.x -= 8u;
CurrFilter(gxy);
}

View File

@ -0,0 +1,37 @@
#version 430 core
precision mediump float;
layout (local_size_x = 64) in;
layout(rgba8, binding = 0, location=0) uniform image2D imgOutput;
layout( location=1 ) uniform sampler2D source;
layout( location=2 ) uniform float sharpening;
#define A_GPU 1
#define A_GLSL 1
#include "ffx_a.h"
#define FSR_RCAS_F 1
AU4 con0;
AF4 FsrRcasLoadF(ASU2 p) { return AF4(texelFetch(source, p, 0)); }
void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {}
#include "ffx_fsr1.h"
void CurrFilter(AU2 pos)
{
AF3 c;
FsrRcasF(c.r, c.g, c.b, pos, con0);
imageStore(imgOutput, ASU2(pos), AF4(c, 1));
}
void main() {
FsrRcasCon(con0, sharpening);
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
CurrFilter(gxy);
gxy.x += 8u;
CurrFilter(gxy);
gxy.y += 8u;
CurrFilter(gxy);
gxy.x -= 8u;
CurrFilter(gxy);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
layout(rgba8, binding = 0) uniform image2D imgOutput;
uniform sampler2D inputTexture;
layout( location=0 ) uniform vec2 invResolution;
uniform sampler2D samplerArea;
uniform sampler2D samplerSearch;
void main() {
ivec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4);
for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
ivec2 texelCoord = ivec2(loc.x + i, loc.y + j);
vec2 coord = (texelCoord + vec2(0.5)) / invResolution;
vec2 pixCoord;
vec4 offset[3];
SMAABlendingWeightCalculationVS(coord, pixCoord, offset);
vec4 oColor = SMAABlendingWeightCalculationPS(coord, pixCoord, offset, inputTexture, samplerArea, samplerSearch, ivec4(0));
imageStore(imgOutput, texelCoord, oColor);
}
}
}

View File

@ -0,0 +1,24 @@
layout(rgba8, binding = 0) uniform image2D imgOutput;
uniform sampler2D inputTexture;
layout( location=0 ) uniform vec2 invResolution;
void main()
{
vec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4);
for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
ivec2 texelCoord = ivec2(loc.x + i, loc.y + j);
vec2 coord = (texelCoord + vec2(0.5)) / invResolution;
vec4 offset[3];
SMAAEdgeDetectionVS(coord, offset);
vec2 oColor = SMAAColorEdgeDetectionPS(coord, offset, inputTexture);
if (oColor != float2(-2.0, -2.0))
{
imageStore(imgOutput, texelCoord, vec4(oColor, 0.0, 1.0));
}
}
}
}

View File

@ -0,0 +1,26 @@
layout(rgba8, binding = 0) uniform image2D imgOutput;
uniform sampler2D inputTexture;
layout( location=0 ) uniform vec2 invResolution;
uniform sampler2D samplerBlend;
void main() {
vec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4);
for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
ivec2 texelCoord = ivec2(loc.x + i, loc.y + j);
vec2 coord = (texelCoord + vec2(0.5)) / invResolution;
vec2 pixCoord;
vec4 offset;
SMAANeighborhoodBlendingVS(coord, offset);
vec4 oColor = SMAANeighborhoodBlendingPS(coord, offset, inputTexture, samplerBlend);
imageStore(imgOutput, texelCoord, oColor);
}
}
}

View File

@ -0,0 +1,261 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Image;
using System;
namespace Ryujinx.Graphics.OpenGL.Effects.Smaa
{
internal partial class SmaaPostProcessingEffect : IPostProcessingEffect
{
public const int AreaWidth = 160;
public const int AreaHeight = 560;
public const int SearchWidth = 64;
public const int SearchHeight = 16;
private readonly OpenGLRenderer _renderer;
private TextureStorage _outputTexture;
private TextureStorage _searchTexture;
private TextureStorage _areaTexture;
private int[] _edgeShaderPrograms;
private int[] _blendShaderPrograms;
private int[] _neighbourShaderPrograms;
private TextureStorage _edgeOutputTexture;
private TextureStorage _blendOutputTexture;
private string[] _qualities;
private int _inputUniform;
private int _outputUniform;
private int _samplerAreaUniform;
private int _samplerSearchUniform;
private int _samplerBlendUniform;
private int _resolutionUniform;
private int _quality = 1;
public int Quality
{
get => _quality; set
{
_quality = Math.Clamp(value, 0, _qualities.Length - 1);
}
}
public SmaaPostProcessingEffect(OpenGLRenderer renderer, int quality)
{
_renderer = renderer;
_edgeShaderPrograms = Array.Empty<int>();
_blendShaderPrograms = Array.Empty<int>();
_neighbourShaderPrograms = Array.Empty<int>();
_qualities = new string[] { "SMAA_PRESET_LOW", "SMAA_PRESET_MEDIUM", "SMAA_PRESET_HIGH", "SMAA_PRESET_ULTRA" };
Quality = quality;
Initialize();
}
public void Dispose()
{
_searchTexture?.Dispose();
_areaTexture?.Dispose();
_outputTexture?.Dispose();
_edgeOutputTexture?.Dispose();
_blendOutputTexture?.Dispose();
DeleteShaders();
}
private void DeleteShaders()
{
for (int i = 0; i < _edgeShaderPrograms.Length; i++)
{
GL.DeleteProgram(_edgeShaderPrograms[i]);
GL.DeleteProgram(_blendShaderPrograms[i]);
GL.DeleteProgram(_neighbourShaderPrograms[i]);
}
}
private unsafe void RecreateShaders(int width, int height)
{
string baseShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa.hlsl");
var pixelSizeDefine = $"#define SMAA_RT_METRICS float4(1.0 / {width}.0, 1.0 / {height}.0, {width}, {height}) \n";
_edgeShaderPrograms = new int[_qualities.Length];
_blendShaderPrograms = new int[_qualities.Length];
_neighbourShaderPrograms = new int[_qualities.Length];
for (int i = 0; i < +_edgeShaderPrograms.Length; i++)
{
var presets = $"#version 430 core \n#define {_qualities[i]} 1 \n{pixelSizeDefine}#define SMAA_GLSL_4 1 \nlayout (local_size_x = 16, local_size_y = 16) in;\n{baseShader}";
var edgeShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_edge.glsl");
var blendShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_blend.glsl");
var neighbourShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_neighbour.glsl");
var shaders = new string[] { presets, edgeShaderData };
var edgeProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader);
shaders[1] = blendShaderData;
var blendProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader);
shaders[1] = neighbourShaderData;
var neighbourProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader);
_edgeShaderPrograms[i] = edgeProgram;
_blendShaderPrograms[i] = blendProgram;
_neighbourShaderPrograms[i] = neighbourProgram;
}
_inputUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "inputTexture");
_outputUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "imgOutput");
_samplerAreaUniform = GL.GetUniformLocation(_blendShaderPrograms[0], "samplerArea");
_samplerSearchUniform = GL.GetUniformLocation(_blendShaderPrograms[0], "samplerSearch");
_samplerBlendUniform = GL.GetUniformLocation(_neighbourShaderPrograms[0], "samplerBlend");
_resolutionUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "invResolution");
}
private void Initialize()
{
var areaInfo = new TextureCreateInfo(AreaWidth,
AreaHeight,
1,
1,
1,
1,
1,
1,
Format.R8G8Unorm,
DepthStencilMode.Depth,
Target.Texture2D,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha);
var searchInfo = new TextureCreateInfo(SearchWidth,
SearchHeight,
1,
1,
1,
1,
1,
1,
Format.R8Unorm,
DepthStencilMode.Depth,
Target.Texture2D,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha);
_areaTexture = new TextureStorage(_renderer, areaInfo, 1);
_searchTexture = new TextureStorage(_renderer, searchInfo, 1);
var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin");
var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin");
var areaView = _areaTexture.CreateDefaultView();
var searchView = _searchTexture.CreateDefaultView();
areaView.SetData(areaTexture);
searchView.SetData(searchTexture);
}
public TextureView Run(TextureView view, int width, int height)
{
if (_outputTexture == null || _outputTexture.Info.Width != view.Width || _outputTexture.Info.Height != view.Height)
{
_outputTexture?.Dispose();
_outputTexture = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
_outputTexture.CreateDefaultView();
_edgeOutputTexture = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
_edgeOutputTexture.CreateDefaultView();
_blendOutputTexture = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
_blendOutputTexture.CreateDefaultView();
DeleteShaders();
RecreateShaders(view.Width, view.Height);
}
var textureView = _outputTexture.CreateView(view.Info, 0, 0) as TextureView;
var edgeOutput = _edgeOutputTexture.DefaultView as TextureView;
var blendOutput = _blendOutputTexture.DefaultView as TextureView;
var areaTexture = _areaTexture.DefaultView as TextureView;
var searchTexture = _searchTexture.DefaultView as TextureView;
var previousFramebuffer = GL.GetInteger(GetPName.FramebufferBinding);
int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
GL.ActiveTexture(TextureUnit.Texture0);
int previousTextureBinding0 = GL.GetInteger(GetPName.TextureBinding2D);
GL.ActiveTexture(TextureUnit.Texture1);
int previousTextureBinding1 = GL.GetInteger(GetPName.TextureBinding2D);
GL.ActiveTexture(TextureUnit.Texture2);
int previousTextureBinding2 = GL.GetInteger(GetPName.TextureBinding2D);
var framebuffer = new Framebuffer();
framebuffer.Bind();
framebuffer.AttachColor(0, edgeOutput);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.ClearColor(0, 0, 0, 0);
framebuffer.AttachColor(0, blendOutput);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.ClearColor(0, 0, 0, 0);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, previousFramebuffer);
framebuffer.Dispose();
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
GL.BindImageTexture(0, edgeOutput.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
GL.UseProgram(_edgeShaderPrograms[Quality]);
view.Bind(0);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
GL.BindImageTexture(0, blendOutput.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
GL.UseProgram(_blendShaderPrograms[Quality]);
edgeOutput.Bind(0);
areaTexture.Bind(1);
searchTexture.Bind(2);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform1(_samplerAreaUniform, 1);
GL.Uniform1(_samplerSearchUniform, 2);
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
GL.UseProgram(_neighbourShaderPrograms[Quality]);
view.Bind(0);
blendOutput.Bind(1);
GL.Uniform1(_inputUniform, 0);
GL.Uniform1(_outputUniform, 0);
GL.Uniform1(_samplerBlendUniform, 1);
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
GL.DispatchCompute(dispatchX, dispatchY, 1);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
(_renderer.Pipeline as Pipeline).RestoreImages1And2();
GL.UseProgram(previousProgram);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding0);
GL.ActiveTexture(TextureUnit.Texture1);
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding1);
GL.ActiveTexture(TextureUnit.Texture2);
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding2);
GL.ActiveTexture((TextureUnit)previousUnit);
return textureView;
}
}
}

View File

@ -9,6 +9,20 @@
<PackageReference Include="OpenTK.Graphics" /> <PackageReference Include="OpenTK.Graphics" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Effects\Textures\SmaaAreaTexture.bin" />
<EmbeddedResource Include="Effects\Textures\SmaaSearchTexture.bin" />
<EmbeddedResource Include="Effects\Shaders\fsr_sharpening.glsl" />
<EmbeddedResource Include="Effects\Shaders\fxaa.glsl" />
<EmbeddedResource Include="Effects\Shaders\smaa.hlsl" />
<EmbeddedResource Include="Effects\Shaders\smaa_blend.glsl" />
<EmbeddedResource Include="Effects\Shaders\smaa_edge.glsl" />
<EmbeddedResource Include="Effects\Shaders\smaa_neighbour.glsl" />
<EmbeddedResource Include="Effects\Shaders\ffx_fsr1.h" />
<EmbeddedResource Include="Effects\Shaders\ffx_a.h" />
<EmbeddedResource Include="Effects\Shaders\fsr_scaling.glsl" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" /> <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />

View File

@ -1,5 +1,7 @@
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Effects;
using Ryujinx.Graphics.OpenGL.Effects.Smaa;
using Ryujinx.Graphics.OpenGL.Image; using Ryujinx.Graphics.OpenGL.Image;
using System; using System;
@ -7,14 +9,24 @@ namespace Ryujinx.Graphics.OpenGL
{ {
class Window : IWindow, IDisposable class Window : IWindow, IDisposable
{ {
private const int TextureCount = 3;
private readonly OpenGLRenderer _renderer; private readonly OpenGLRenderer _renderer;
private bool _initialized; private bool _initialized;
private int _width; private int _width;
private int _height; private int _height;
private bool _updateSize;
private int _copyFramebufferHandle; private int _copyFramebufferHandle;
private IPostProcessingEffect _antiAliasing;
private IScalingFilter _scalingFilter;
private bool _isLinear;
private AntiAliasing _currentAntiAliasing;
private bool _updateEffect;
private ScalingFilter _currentScalingFilter;
private float _scalingFilterLevel;
private bool _updateScalingFilter;
private bool _isBgra;
private TextureView _upscaledTexture;
internal BackgroundContextWorker BackgroundContext { get; private set; } internal BackgroundContextWorker BackgroundContext { get; private set; }
@ -48,6 +60,8 @@ namespace Ryujinx.Graphics.OpenGL
{ {
_width = width; _width = width;
_height = height; _height = height;
_updateSize = true;
} }
private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop, Action swapBuffersCallback) private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop, Action swapBuffersCallback)
@ -57,6 +71,32 @@ namespace Ryujinx.Graphics.OpenGL
TextureView viewConverted = view.Format.IsBgr() ? _renderer.TextureCopy.BgraSwap(view) : view; TextureView viewConverted = view.Format.IsBgr() ? _renderer.TextureCopy.BgraSwap(view) : view;
UpdateEffect();
if (_antiAliasing != null)
{
var oldView = viewConverted;
viewConverted = _antiAliasing.Run(viewConverted, _width, _height);
if (viewConverted.Format.IsBgr())
{
var swappedView = _renderer.TextureCopy.BgraSwap(viewConverted);
viewConverted?.Dispose();
viewConverted = swappedView;
}
if (viewConverted != oldView && oldView != view)
{
oldView.Dispose();
}
}
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);
GL.FramebufferTexture( GL.FramebufferTexture(
FramebufferTarget.ReadFramebuffer, FramebufferTarget.ReadFramebuffer,
FramebufferAttachment.ColorAttachment0, FramebufferAttachment.ColorAttachment0,
@ -71,12 +111,12 @@ namespace Ryujinx.Graphics.OpenGL
GL.Clear(ClearBufferMask.ColorBufferBit); GL.Clear(ClearBufferMask.ColorBufferBit);
int srcX0, srcX1, srcY0, srcY1; int srcX0, srcX1, srcY0, srcY1;
float scale = view.ScaleFactor; float scale = viewConverted.ScaleFactor;
if (crop.Left == 0 && crop.Right == 0) if (crop.Left == 0 && crop.Right == 0)
{ {
srcX0 = 0; srcX0 = 0;
srcX1 = (int)(view.Width / scale); srcX1 = (int)(viewConverted.Width / scale);
} }
else else
{ {
@ -87,7 +127,7 @@ namespace Ryujinx.Graphics.OpenGL
if (crop.Top == 0 && crop.Bottom == 0) if (crop.Top == 0 && crop.Bottom == 0)
{ {
srcY0 = 0; srcY0 = 0;
srcY1 = (int)(view.Height / scale); srcY1 = (int)(viewConverted.Height / scale);
} }
else else
{ {
@ -125,6 +165,42 @@ namespace Ryujinx.Graphics.OpenGL
ScreenCaptureRequested = false; ScreenCaptureRequested = false;
} }
if (_scalingFilter != null)
{
if (viewConverted.Format.IsBgr() && !_isBgra)
{
RecreateUpscalingTexture(true);
}
_scalingFilter.Run(
viewConverted,
_upscaledTexture,
_width,
_height,
new Extents2D(
srcX0,
srcY0,
srcX1,
srcY1),
new Extents2D(
dstX0,
dstY0,
dstX1,
dstY1)
);
srcX0 = dstX0;
srcY0 = dstY0;
srcX1 = dstX1;
srcY1 = dstY1;
GL.FramebufferTexture(
FramebufferTarget.ReadFramebuffer,
FramebufferAttachment.ColorAttachment0,
_upscaledTexture.Handle,
0);
}
GL.BlitFramebuffer( GL.BlitFramebuffer(
srcX0, srcX0,
srcY0, srcY0,
@ -135,7 +211,7 @@ namespace Ryujinx.Graphics.OpenGL
dstX1, dstX1,
dstY1, dstY1,
ClearBufferMask.ColorBufferBit, ClearBufferMask.ColorBufferBit,
BlitFramebufferFilter.Linear); _isLinear ? BlitFramebufferFilter.Linear : BlitFramebufferFilter.Nearest);
// Remove Alpha channel // Remove Alpha channel
GL.ColorMask(false, false, false, true); GL.ColorMask(false, false, false, true);
@ -209,6 +285,135 @@ namespace Ryujinx.Graphics.OpenGL
_copyFramebufferHandle = 0; _copyFramebufferHandle = 0;
} }
_antiAliasing?.Dispose();
_scalingFilter?.Dispose();
_upscaledTexture?.Dispose();
}
public void SetAntiAliasing(AntiAliasing effect)
{
if (_currentAntiAliasing == effect && _antiAliasing != null)
{
return;
}
_currentAntiAliasing = effect;
_updateEffect = true;
}
public void SetScalingFilter(ScalingFilter type)
{
if (_currentScalingFilter == type && _antiAliasing != null)
{
return;
}
_currentScalingFilter = type;
_updateScalingFilter = true;
}
private void UpdateEffect()
{
if (_updateEffect)
{
_updateEffect = false;
switch (_currentAntiAliasing)
{
case AntiAliasing.Fxaa:
_antiAliasing?.Dispose();
_antiAliasing = new FxaaPostProcessingEffect(_renderer);
break;
case AntiAliasing.None:
_antiAliasing?.Dispose();
_antiAliasing = null;
break;
case AntiAliasing.SmaaLow:
case AntiAliasing.SmaaMedium:
case AntiAliasing.SmaaHigh:
case AntiAliasing.SmaaUltra:
var quality = _currentAntiAliasing - AntiAliasing.SmaaLow;
if (_antiAliasing is SmaaPostProcessingEffect smaa)
{
smaa.Quality = quality;
}
else
{
_antiAliasing?.Dispose();
_antiAliasing = new SmaaPostProcessingEffect(_renderer, quality);
}
break;
}
}
if (_updateSize && !_updateScalingFilter)
{
RecreateUpscalingTexture();
}
_updateSize = false;
if (_updateScalingFilter)
{
_updateScalingFilter = false;
switch (_currentScalingFilter)
{
case ScalingFilter.Bilinear:
case ScalingFilter.Nearest:
_scalingFilter?.Dispose();
_scalingFilter = null;
_isLinear = _currentScalingFilter == ScalingFilter.Bilinear;
_upscaledTexture?.Dispose();
_upscaledTexture = null;
break;
case ScalingFilter.Fsr:
if (_scalingFilter is not FsrScalingFilter)
{
_scalingFilter?.Dispose();
_scalingFilter = new FsrScalingFilter(_renderer, _antiAliasing);
}
_isLinear = false;
_scalingFilter.Level = _scalingFilterLevel;
RecreateUpscalingTexture();
break;
}
}
}
private void RecreateUpscalingTexture(bool forceBgra = false)
{
_upscaledTexture?.Dispose();
var info = new TextureCreateInfo(
_width,
_height,
1,
1,
1,
1,
1,
1,
Format.R8G8B8A8Unorm,
DepthStencilMode.Depth,
Target.Texture2D,
forceBgra ? SwizzleComponent.Blue : SwizzleComponent.Red,
SwizzleComponent.Green,
forceBgra ? SwizzleComponent.Red : SwizzleComponent.Blue,
SwizzleComponent.Alpha);
_isBgra = forceBgra;
_upscaledTexture = _renderer.CreateTexture(info, 1) as TextureView;
}
public void SetScalingFilterLevel(float level)
{
_scalingFilterLevel = level;
_updateScalingFilter = true;
} }
} }
} }

View File

@ -163,6 +163,13 @@ namespace Ryujinx.Graphics.Vulkan
SignalDirty(DirtyFlags.Image); SignalDirty(DirtyFlags.Image);
} }
public void SetImage(int binding, Auto<DisposableImageView> image)
{
_imageRefs[binding] = image;
SignalDirty(DirtyFlags.Image);
}
public void SetStorageBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers) public void SetStorageBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
{ {
for (int i = 0; i < buffers.Length; i++) for (int i = 0; i < buffers.Length; i++)

View File

@ -0,0 +1,208 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using Silk.NET.Vulkan;
using System;
using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
namespace Ryujinx.Graphics.Vulkan.Effects
{
internal partial class FsrScalingFilter : IScalingFilter
{
private readonly VulkanRenderer _renderer;
private PipelineHelperShader _pipeline;
private ISampler _sampler;
private ShaderCollection _scalingProgram;
private ShaderCollection _sharpeningProgram;
private float _sharpeningLevel = 1;
private Device _device;
private TextureView _intermediaryTexture;
public float Level
{
get => _sharpeningLevel;
set
{
_sharpeningLevel = MathF.Max(0.01f, value);
}
}
public FsrScalingFilter(VulkanRenderer renderer, Device device)
{
_device = device;
_renderer = renderer;
Initialize();
}
public void Dispose()
{
_pipeline.Dispose();
_scalingProgram.Dispose();
_sharpeningProgram.Dispose();
_sampler.Dispose();
_intermediaryTexture?.Dispose();
}
public void Initialize()
{
_pipeline = new PipelineHelperShader(_renderer, _device);
_pipeline.Initialize();
var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv");
var sharpeningShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv");
var computeBindings = new ShaderBindings(
new[] { 2 },
Array.Empty<int>(),
new[] { 1 },
new[] { 0 });
var sharpeningBindings = new ShaderBindings(
new[] { 2, 3, 4 },
Array.Empty<int>(),
new[] { 1 },
new[] { 0 });
_sampler = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
_scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(scalingShader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
});
_sharpeningProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(sharpeningShader, sharpeningBindings, ShaderStage.Compute, TargetLanguage.Spirv)
});
}
public void Run(
TextureView view,
CommandBufferScoped cbs,
Auto<DisposableImageView> destinationTexture,
Silk.NET.Vulkan.Format format,
int width,
int height,
Extent2D source,
Extent2D destination)
{
if (_intermediaryTexture == null
|| _intermediaryTexture.Info.Width != width
|| _intermediaryTexture.Info.Height != height
|| !_intermediaryTexture.Info.Equals(view.Info))
{
var originalInfo = view.Info;
var swapRB = originalInfo.Format.IsBgr() && originalInfo.SwizzleR == SwizzleComponent.Red;
var info = new TextureCreateInfo(
width,
height,
originalInfo.Depth,
originalInfo.Levels,
originalInfo.Samples,
originalInfo.BlockWidth,
originalInfo.BlockHeight,
originalInfo.BytesPerPixel,
originalInfo.Format,
originalInfo.DepthStencilMode,
originalInfo.Target,
swapRB ? originalInfo.SwizzleB : originalInfo.SwizzleR,
originalInfo.SwizzleG,
swapRB ? originalInfo.SwizzleR : originalInfo.SwizzleB,
originalInfo.SwizzleA);
_intermediaryTexture?.Dispose();
_intermediaryTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
}
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
viewports[0] = new GAL.Viewport(
new Rectangle<float>(0, 0, view.Width, view.Height),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
scissors[0] = new Rectangle<int>(0, 0, view.Width, view.Height);
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_scalingProgram);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _sampler);
float srcWidth = Math.Abs(source.X2 - source.X1);
float srcHeight = Math.Abs(source.Y2 - source.Y1);
float scaleX = srcWidth / view.Width;
float scaleY = srcHeight / view.Height;
ReadOnlySpan<float> dimensionsBuffer = stackalloc float[]
{
source.X1,
source.X2,
source.Y1,
source.Y2,
destination.X1,
destination.X2,
destination.Y1,
destination.Y2,
scaleX,
scaleY
};
int rangeSize = dimensionsBuffer.Length * sizeof(float);
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
_renderer.BufferManager.SetData(bufferHandle, 0, dimensionsBuffer);
ReadOnlySpan<float> sharpeningBuffer = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f)};
var sharpeningBufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, sizeof(float), false);
_renderer.BufferManager.SetData(sharpeningBufferHandle, 0, sharpeningBuffer);
int threadGroupWorkRegionDim = 16;
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, _intermediaryTexture, GAL.Format.R8G8B8A8Unorm);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
viewports[0] = new GAL.Viewport(
new Rectangle<float>(0, 0, width, height),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
scissors[0] = new Rectangle<int>(0, 0, width, height);
// Sharpening pass
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_sharpeningProgram);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
var sharpeningRange = new BufferRange(sharpeningBufferHandle, 0, sizeof(float));
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningRange) });
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, destinationTexture);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
_pipeline.Finish();
_renderer.BufferManager.Delete(bufferHandle);
_renderer.BufferManager.Delete(sharpeningBufferHandle);
}
}
}

View File

@ -0,0 +1,127 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using Silk.NET.Vulkan;
using System;
namespace Ryujinx.Graphics.Vulkan.Effects
{
internal partial class FxaaPostProcessingEffect : IPostProcessingEffect
{
private readonly VulkanRenderer _renderer;
private ISampler _samplerLinear;
private ShaderCollection _shaderProgram;
private PipelineHelperShader _pipeline;
private TextureView _texture;
public FxaaPostProcessingEffect(VulkanRenderer renderer, Device device)
{
_renderer = renderer;
_pipeline = new PipelineHelperShader(renderer, device);
Initialize();
}
public void Dispose()
{
_shaderProgram.Dispose();
_pipeline.Dispose();
_samplerLinear.Dispose();
_texture?.Dispose();
}
private void Initialize()
{
_pipeline.Initialize();
var shader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv");
var computeBindings = new ShaderBindings(
new[] { 2 },
Array.Empty<int>(),
new[] { 1 },
new[] { 0 });
_samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
_shaderProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(shader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
});
}
public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)
{
if (_texture == null || _texture.Width != view.Width || _texture.Height != view.Height)
{
_texture?.Dispose();
var info = view.Info;
if (view.Info.Format.IsBgr())
{
info = new TextureCreateInfo(info.Width,
info.Height,
info.Depth,
info.Levels,
info.Samples,
info.BlockWidth,
info.BlockHeight,
info.BytesPerPixel,
info.Format,
info.DepthStencilMode,
info.Target,
info.SwizzleB,
info.SwizzleG,
info.SwizzleR,
info.SwizzleA);
}
_texture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
}
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_shaderProgram);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
int rangeSize = resolutionBuffer.Length * sizeof(float);
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
viewports[0] = new GAL.Viewport(
new Rectangle<float>(0, 0, view.Width, view.Height),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
_pipeline.SetScissors(stackalloc[] { new Rectangle<int>(0, 0, view.Width, view.Height) });
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, _texture, GAL.Format.R8G8B8A8Unorm);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_renderer.BufferManager.Delete(bufferHandle);
_pipeline.ComputeBarrier();
_pipeline.Finish();
return _texture;
}
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace Ryujinx.Graphics.Vulkan.Effects
{
internal interface IPostProcessingEffect : IDisposable
{
const int LocalGroupSize = 64;
TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height);
}
}

View File

@ -0,0 +1,20 @@
using Silk.NET.Vulkan;
using System;
using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
namespace Ryujinx.Graphics.Vulkan.Effects
{
internal interface IScalingFilter : IDisposable
{
float Level { get; set; }
void Run(
TextureView view,
CommandBufferScoped cbs,
Auto<DisposableImageView> destinationTexture,
Format format,
int width,
int height,
Extent2D source,
Extent2D destination);
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Vulkan.Effects
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct SmaaConstants
{
public int QualityLow;
public int QualityMedium;
public int QualityHigh;
public int QualityUltra;
public float Width;
public float Height;
}
}

View File

@ -0,0 +1,314 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using Silk.NET.Vulkan;
using System;
using Format = Ryujinx.Graphics.GAL.Format;
namespace Ryujinx.Graphics.Vulkan.Effects
{
internal partial class SmaaPostProcessingEffect : IPostProcessingEffect
{
public const int AreaWidth = 160;
public const int AreaHeight = 560;
public const int SearchWidth = 64;
public const int SearchHeight = 16;
private readonly VulkanRenderer _renderer;
private ISampler _samplerLinear;
private SmaaConstants _specConstants;
private ShaderCollection _edgeProgram;
private ShaderCollection _blendProgram;
private ShaderCollection _neighbourProgram;
private PipelineHelperShader _pipeline;
private TextureView _outputTexture;
private TextureView _edgeOutputTexture;
private TextureView _blendOutputTexture;
private TextureView _areaTexture;
private TextureView _searchTexture;
private Device _device;
private bool _recreatePipelines;
private int _quality;
public SmaaPostProcessingEffect(VulkanRenderer renderer, Device device, int quality)
{
_device = device;
_renderer = renderer;
_quality = quality;
Initialize();
}
public int Quality
{
get => _quality;
set
{
_quality = value;
_recreatePipelines = true;
}
}
public void Dispose()
{
DeletePipelines();
_samplerLinear?.Dispose();
_outputTexture?.Dispose();
_edgeOutputTexture?.Dispose();
_blendOutputTexture?.Dispose();
_areaTexture?.Dispose();
_searchTexture?.Dispose();
}
private unsafe void RecreateShaders(int width, int height)
{
_recreatePipelines = false;
DeletePipelines();
_pipeline = new PipelineHelperShader(_renderer, _device);
_pipeline.Initialize();
var edgeShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.spv");
var blendShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv");
var neighbourShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv");
var edgeBindings = new ShaderBindings(
new[] { 2 },
Array.Empty<int>(),
new[] { 1 },
new[] { 0 });
var blendBindings = new ShaderBindings(
new[] { 2 },
Array.Empty<int>(),
new[] { 1, 3, 4 },
new[] { 0 });
var neighbourBindings = new ShaderBindings(
new[] { 2 },
Array.Empty<int>(),
new[] { 1, 3 },
new[] { 0 });
_samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
_specConstants = new SmaaConstants()
{
Width = width,
Height = height,
QualityLow = Quality == 0 ? 1 : 0,
QualityMedium = Quality == 1 ? 1 : 0,
QualityHigh = Quality == 2 ? 1 : 0,
QualityUltra = Quality == 3 ? 1 : 0,
};
var specInfo = new SpecDescription(
(0, SpecConstType.Int32),
(1, SpecConstType.Int32),
(2, SpecConstType.Int32),
(3, SpecConstType.Int32),
(4, SpecConstType.Float32),
(5, SpecConstType.Float32));
_edgeProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(edgeShader, edgeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
}, new[] { specInfo });
_blendProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(blendShader, blendBindings, ShaderStage.Compute, TargetLanguage.Spirv)
}, new[] { specInfo });
_neighbourProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(neighbourShader, neighbourBindings, ShaderStage.Compute, TargetLanguage.Spirv)
}, new[] { specInfo });
}
public void DeletePipelines()
{
_pipeline?.Dispose();
_edgeProgram?.Dispose();
_blendProgram?.Dispose();
_neighbourProgram?.Dispose();
}
private void Initialize()
{
var areaInfo = new TextureCreateInfo(AreaWidth,
AreaHeight,
1,
1,
1,
1,
1,
1,
Format.R8G8Unorm,
DepthStencilMode.Depth,
Target.Texture2D,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha);
var searchInfo = new TextureCreateInfo(SearchWidth,
SearchHeight,
1,
1,
1,
1,
1,
1,
Format.R8Unorm,
DepthStencilMode.Depth,
Target.Texture2D,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha);
var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin");
var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin");
_areaTexture = _renderer.CreateTexture(areaInfo, 1) as TextureView;
_searchTexture = _renderer.CreateTexture(searchInfo, 1) as TextureView;
_areaTexture.SetData(areaTexture);
_searchTexture.SetData(searchTexture);
}
public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)
{
if (_recreatePipelines || _outputTexture == null || _outputTexture.Info.Width != view.Width || _outputTexture.Info.Height != view.Height)
{
RecreateShaders(view.Width, view.Height);
_outputTexture?.Dispose();
_edgeOutputTexture?.Dispose();
_blendOutputTexture?.Dispose();
var info = view.Info;
if (view.Info.Format.IsBgr())
{
info = new TextureCreateInfo(info.Width,
info.Height,
info.Depth,
info.Levels,
info.Samples,
info.BlockWidth,
info.BlockHeight,
info.BytesPerPixel,
info.Format,
info.DepthStencilMode,
info.Target,
info.SwizzleB,
info.SwizzleG,
info.SwizzleR,
info.SwizzleA);
}
_outputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
_edgeOutputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
_blendOutputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
}
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
viewports[0] = new GAL.Viewport(
new Rectangle<float>(0, 0, view.Width, view.Height),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
scissors[0] = new Rectangle<int>(0, 0, view.Width, view.Height);
_renderer.HelperShader.Clear(_renderer,
_edgeOutputTexture.GetImageView(),
new float[] { 0, 0, 0, 1 },
(uint)(ColorComponentFlags.RBit | ColorComponentFlags.GBit | ColorComponentFlags.BBit | ColorComponentFlags.ABit),
view.Width,
view.Height,
_edgeOutputTexture.VkFormat,
ComponentType.UnsignedInteger,
scissors[0]);
_renderer.HelperShader.Clear(_renderer,
_blendOutputTexture.GetImageView(),
new float[] { 0, 0, 0, 1 },
(uint)(ColorComponentFlags.RBit | ColorComponentFlags.GBit | ColorComponentFlags.BBit | ColorComponentFlags.ABit),
view.Width,
view.Height,
_blendOutputTexture.VkFormat,
ComponentType.UnsignedInteger,
scissors[0]);
_renderer.Pipeline.TextureBarrier();
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
// Edge pass
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_edgeProgram);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
_pipeline.Specialize(_specConstants);
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
int rangeSize = resolutionBuffer.Length * sizeof(float);
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, _edgeOutputTexture, GAL.Format.R8G8B8A8Unorm);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
// Blend pass
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_blendProgram);
_pipeline.Specialize(_specConstants);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, _blendOutputTexture, GAL.Format.R8G8B8A8Unorm);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
// Neighbour pass
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_neighbourProgram);
_pipeline.Specialize(_specConstants);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, _outputTexture, GAL.Format.R8G8B8A8Unorm);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
_pipeline.Finish();
_renderer.BufferManager.Delete(bufferHandle);
return _outputTexture;
}
}
}

View File

@ -38,8 +38,11 @@ namespace Ryujinx.Graphics.Vulkan
public void Dispose() public void Dispose()
{ {
Marshal.FreeHGlobal((IntPtr)Pointer); if (Pointer != null)
Pointer = null; {
Marshal.FreeHGlobal((IntPtr)Pointer);
Pointer = null;
}
} }
} }
} }

View File

@ -150,6 +150,28 @@ namespace Ryujinx.Graphics.Vulkan
null); null);
} }
public void ComputeBarrier()
{
MemoryBarrier memoryBarrier = new MemoryBarrier()
{
SType = StructureType.MemoryBarrier,
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit
};
Gd.Api.CmdPipelineBarrier(
CommandBuffer,
PipelineStageFlags.ComputeShaderBit,
PipelineStageFlags.AllCommandsBit,
0,
1,
new ReadOnlySpan<MemoryBarrier>(memoryBarrier),
0,
ReadOnlySpan<BufferMemoryBarrier>.Empty,
0,
ReadOnlySpan<ImageMemoryBarrier>.Empty);
}
public void BeginTransformFeedback(GAL.PrimitiveTopology topology) public void BeginTransformFeedback(GAL.PrimitiveTopology topology)
{ {
_tfEnabled = true; _tfEnabled = true;
@ -803,6 +825,11 @@ namespace Ryujinx.Graphics.Vulkan
_descriptorSetUpdater.SetImage(binding, image, imageFormat); _descriptorSetUpdater.SetImage(binding, image, imageFormat);
} }
public void SetImage(int binding, Auto<DisposableImageView> image)
{
_descriptorSetUpdater.SetImage(binding, image);
}
public void SetIndexBuffer(BufferRange buffer, GAL.IndexType type) public void SetIndexBuffer(BufferRange buffer, GAL.IndexType type)
{ {
if (buffer.Handle != BufferHandle.Null) if (buffer.Handle != BufferHandle.Null)

View File

@ -12,6 +12,17 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Effects\Textures\SmaaAreaTexture.bin" />
<EmbeddedResource Include="Effects\Textures\SmaaSearchTexture.bin" />
<EmbeddedResource Include="Effects\Shaders\FsrScaling.spv" />
<EmbeddedResource Include="Effects\Shaders\FsrSharpening.spv" />
<EmbeddedResource Include="Effects\Shaders\Fxaa.spv" />
<EmbeddedResource Include="Effects\Shaders\SmaaBlend.spv" />
<EmbeddedResource Include="Effects\Shaders\SmaaEdge.spv" />
<EmbeddedResource Include="Effects\Shaders\SmaaNeighbour.spv" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" /> <PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" />
<PackageReference Include="shaderc.net" /> <PackageReference Include="shaderc.net" />

View File

@ -1,4 +1,5 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Vulkan.Effects;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR; using Silk.NET.Vulkan.Extensions.KHR;
using System; using System;
@ -29,6 +30,14 @@ namespace Ryujinx.Graphics.Vulkan
private bool _vsyncEnabled; private bool _vsyncEnabled;
private bool _vsyncModeChanged; private bool _vsyncModeChanged;
private VkFormat _format; private VkFormat _format;
private AntiAliasing _currentAntiAliasing;
private bool _updateEffect;
private IPostProcessingEffect _effect;
private IScalingFilter _scalingFilter;
private bool _isLinear;
private float _scalingFilterLevel;
private bool _updateScalingFilter;
private ScalingFilter _currentScalingFilter;
public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device) public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
{ {
@ -116,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageFormat = surfaceFormat.Format, ImageFormat = surfaceFormat.Format,
ImageColorSpace = surfaceFormat.ColorSpace, ImageColorSpace = surfaceFormat.ColorSpace,
ImageExtent = extent, ImageExtent = extent,
ImageUsage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit, ImageUsage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit | ImageUsageFlags.StorageBit,
ImageSharingMode = SharingMode.Exclusive, ImageSharingMode = SharingMode.Exclusive,
ImageArrayLayers = 1, ImageArrayLayers = 1,
PreTransform = capabilities.CurrentTransform, PreTransform = capabilities.CurrentTransform,
@ -280,6 +289,13 @@ namespace Ryujinx.Graphics.Vulkan
var view = (TextureView)texture; var view = (TextureView)texture;
UpdateEffect();
if (_effect != null)
{
view = _effect.Run(view, cbs, _width, _height);
}
int srcX0, srcX1, srcY0, srcY1; int srcX0, srcX1, srcY0, srcY1;
float scale = view.ScaleFactor; float scale = view.ScaleFactor;
@ -315,6 +331,18 @@ namespace Ryujinx.Graphics.Vulkan
if (ScreenCaptureRequested) if (ScreenCaptureRequested)
{ {
if (_effect != null)
{
_gd.CommandBufferPool.Return(
cbs,
null,
stackalloc[] { PipelineStageFlags.ColorAttachmentOutputBit },
null);
_gd.FlushAllCommands();
cbs.GetFence().Wait();
cbs = _gd.CommandBufferPool.Rent();
}
CaptureFrame(view, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, view.Info.Format.IsBgr(), crop.FlipX, crop.FlipY); CaptureFrame(view, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, view.Info.Format.IsBgr(), crop.FlipX, crop.FlipY);
ScreenCaptureRequested = false; ScreenCaptureRequested = false;
@ -335,20 +363,36 @@ namespace Ryujinx.Graphics.Vulkan
int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY; int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY; int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
_gd.HelperShader.BlitColor( if (_scalingFilter != null)
_gd, {
cbs, _scalingFilter.Run(
view, view,
_swapchainImageViews[nextImage], cbs,
_width, _swapchainImageViews[nextImage],
_height, _format,
1, _width,
_format, _height,
false, new Extents2D(srcX0, srcY0, srcX1, srcY1),
new Extents2D(srcX0, srcY0, srcX1, srcY1), new Extents2D(dstX0, dstY0, dstX1, dstY1)
new Extents2D(dstX0, dstY1, dstX1, dstY0), );
true, }
true); else
{
_gd.HelperShader.BlitColor(
_gd,
cbs,
view,
_swapchainImageViews[nextImage],
_width,
_height,
1,
_format,
false,
new Extents2D(srcX0, srcY0, srcX1, srcY1),
new Extents2D(dstX0, dstY1, dstX1, dstY0),
_isLinear,
true);
}
Transition( Transition(
cbs.CommandBuffer, cbs.CommandBuffer,
@ -387,6 +431,95 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
public override void SetAntiAliasing(AntiAliasing effect)
{
if (_currentAntiAliasing == effect && _effect != null)
{
return;
}
_currentAntiAliasing = effect;
_updateEffect = true;
}
public override void SetScalingFilter(ScalingFilter type)
{
if (_currentScalingFilter == type && _effect != null)
{
return;
}
_currentScalingFilter = type;
_updateScalingFilter = true;
}
private void UpdateEffect()
{
if (_updateEffect)
{
_updateEffect = false;
switch (_currentAntiAliasing)
{
case AntiAliasing.Fxaa:
_effect?.Dispose();
_effect = new FxaaPostProcessingEffect(_gd, _device);
break;
case AntiAliasing.None:
_effect?.Dispose();
_effect = null;
break;
case AntiAliasing.SmaaLow:
case AntiAliasing.SmaaMedium:
case AntiAliasing.SmaaHigh:
case AntiAliasing.SmaaUltra:
var quality = _currentAntiAliasing - AntiAliasing.SmaaLow;
if (_effect is SmaaPostProcessingEffect smaa)
{
smaa.Quality = quality;
}
else
{
_effect?.Dispose();
_effect = new SmaaPostProcessingEffect(_gd, _device, quality);
}
break;
}
}
if (_updateScalingFilter)
{
_updateScalingFilter = false;
switch (_currentScalingFilter)
{
case ScalingFilter.Bilinear:
case ScalingFilter.Nearest:
_scalingFilter?.Dispose();
_scalingFilter = null;
_isLinear = _currentScalingFilter == ScalingFilter.Bilinear;
break;
case ScalingFilter.Fsr:
if (_scalingFilter is not FsrScalingFilter)
{
_scalingFilter?.Dispose();
_scalingFilter = new FsrScalingFilter(_gd, _device);
}
_scalingFilter.Level = _scalingFilterLevel;
break;
}
}
}
public override void SetScalingFilterLevel(float level)
{
_scalingFilterLevel = level;
_updateScalingFilter = true;
}
private unsafe void Transition( private unsafe void Transition(
CommandBuffer commandBuffer, CommandBuffer commandBuffer,
Image image, Image image,
@ -456,8 +589,10 @@ namespace Ryujinx.Graphics.Vulkan
} }
_gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null); _gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null);
} }
_effect?.Dispose();
_scalingFilter?.Dispose();
} }
} }

View File

@ -11,5 +11,8 @@ namespace Ryujinx.Graphics.Vulkan
public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback); public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
public abstract void SetSize(int width, int height); public abstract void SetSize(int width, int height);
public abstract void ChangeVSyncMode(bool vsyncEnabled); public abstract void ChangeVSyncMode(bool vsyncEnabled);
public abstract void SetAntiAliasing(AntiAliasing effect);
public abstract void SetScalingFilter(ScalingFilter scalerType);
public abstract void SetScalingFilterLevel(float scale);
} }
} }

View File

@ -14,7 +14,7 @@ namespace Ryujinx.Ui.Common.Configuration
/// <summary> /// <summary>
/// The current version of the file format /// The current version of the file format
/// </summary> /// </summary>
public const int CurrentVersion = 43; public const int CurrentVersion = 44;
/// <summary> /// <summary>
/// Version of the configuration file format /// Version of the configuration file format
@ -51,6 +51,21 @@ namespace Ryujinx.Ui.Common.Configuration
/// </summary> /// </summary>
public AspectRatio AspectRatio { get; set; } public AspectRatio AspectRatio { get; set; }
/// <summary>
/// Applies anti-aliasing to the renderer.
/// </summary>
public AntiAliasing AntiAliasing { get; set; }
/// <summary>
/// Sets the framebuffer upscaling type.
/// </summary>
public ScalingFilter ScalingFilter { get; set; }
/// <summary>
/// Sets the framebuffer upscaling level.
/// </summary>
public int ScalingFilterLevel { get; set; }
/// <summary> /// <summary>
/// Dumps shaders in this local directory /// Dumps shaders in this local directory
/// </summary> /// </summary>

View File

@ -433,6 +433,21 @@ namespace Ryujinx.Ui.Common.Configuration
/// </summary> /// </summary>
public ReactiveObject<GraphicsBackend> GraphicsBackend { get; private set; } public ReactiveObject<GraphicsBackend> GraphicsBackend { get; private set; }
/// <summary>
/// Applies anti-aliasing to the renderer.
/// </summary>
public ReactiveObject<AntiAliasing> AntiAliasing { get; private set; }
/// <summary>
/// Sets the framebuffer upscaling type.
/// </summary>
public ReactiveObject<ScalingFilter> ScalingFilter { get; private set; }
/// <summary>
/// Sets the framebuffer upscaling level.
/// </summary>
public ReactiveObject<int> ScalingFilterLevel { get; private set; }
/// <summary> /// <summary>
/// Preferred GPU /// Preferred GPU
/// </summary> /// </summary>
@ -463,6 +478,12 @@ namespace Ryujinx.Ui.Common.Configuration
PreferredGpu.Event += static (sender, e) => LogValueChange(sender, e, nameof(PreferredGpu)); PreferredGpu.Event += static (sender, e) => LogValueChange(sender, e, nameof(PreferredGpu));
EnableMacroHLE = new ReactiveObject<bool>(); EnableMacroHLE = new ReactiveObject<bool>();
EnableMacroHLE.Event += static (sender, e) => LogValueChange(sender, e, nameof(EnableMacroHLE)); EnableMacroHLE.Event += static (sender, e) => LogValueChange(sender, e, nameof(EnableMacroHLE));
AntiAliasing = new ReactiveObject<AntiAliasing>();
AntiAliasing.Event += static (sender, e) => LogValueChange(sender, e, nameof(AntiAliasing));
ScalingFilter = new ReactiveObject<ScalingFilter>();
ScalingFilter.Event += static (sender, e) => LogValueChange(sender, e, nameof(ScalingFilter));
ScalingFilterLevel = new ReactiveObject<int>();
ScalingFilterLevel.Event += static (sender, e) => LogValueChange(sender, e, nameof(ScalingFilterLevel));
} }
} }
@ -540,6 +561,9 @@ namespace Ryujinx.Ui.Common.Configuration
ResScaleCustom = Graphics.ResScaleCustom, ResScaleCustom = Graphics.ResScaleCustom,
MaxAnisotropy = Graphics.MaxAnisotropy, MaxAnisotropy = Graphics.MaxAnisotropy,
AspectRatio = Graphics.AspectRatio, AspectRatio = Graphics.AspectRatio,
AntiAliasing = Graphics.AntiAliasing,
ScalingFilter = Graphics.ScalingFilter,
ScalingFilterLevel = Graphics.ScalingFilterLevel,
GraphicsShadersDumpPath = Graphics.ShadersDumpPath, GraphicsShadersDumpPath = Graphics.ShadersDumpPath,
LoggingEnableDebug = Logger.EnableDebug, LoggingEnableDebug = Logger.EnableDebug,
LoggingEnableStub = Logger.EnableStub, LoggingEnableStub = Logger.EnableStub,
@ -651,6 +675,9 @@ namespace Ryujinx.Ui.Common.Configuration
Graphics.EnableShaderCache.Value = true; Graphics.EnableShaderCache.Value = true;
Graphics.EnableTextureRecompression.Value = false; Graphics.EnableTextureRecompression.Value = false;
Graphics.EnableMacroHLE.Value = true; Graphics.EnableMacroHLE.Value = true;
Graphics.AntiAliasing.Value = AntiAliasing.None;
Graphics.ScalingFilter.Value = ScalingFilter.Bilinear;
Graphics.ScalingFilterLevel.Value = 80;
System.EnablePtc.Value = true; System.EnablePtc.Value = true;
System.EnableInternetAccess.Value = false; System.EnableInternetAccess.Value = false;
System.EnableFsIntegrityChecks.Value = true; System.EnableFsIntegrityChecks.Value = true;
@ -1208,6 +1235,17 @@ namespace Ryujinx.Ui.Common.Configuration
configurationFileFormat.UseHypervisor = true; configurationFileFormat.UseHypervisor = true;
} }
if (configurationFileFormat.Version < 44)
{
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 42.");
configurationFileFormat.AntiAliasing = AntiAliasing.None;
configurationFileFormat.ScalingFilter = ScalingFilter.Bilinear;
configurationFileFormat.ScalingFilterLevel = 80;
configurationFileUpdated = true;
}
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
@ -1217,6 +1255,9 @@ namespace Ryujinx.Ui.Common.Configuration
Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading; Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading;
Graphics.GraphicsBackend.Value = configurationFileFormat.GraphicsBackend; Graphics.GraphicsBackend.Value = configurationFileFormat.GraphicsBackend;
Graphics.PreferredGpu.Value = configurationFileFormat.PreferredGpu; Graphics.PreferredGpu.Value = configurationFileFormat.PreferredGpu;
Graphics.AntiAliasing.Value = configurationFileFormat.AntiAliasing;
Graphics.ScalingFilter.Value = configurationFileFormat.ScalingFilter;
Graphics.ScalingFilterLevel.Value = configurationFileFormat.ScalingFilterLevel;
Logger.EnableDebug.Value = configurationFileFormat.LoggingEnableDebug; Logger.EnableDebug.Value = configurationFileFormat.LoggingEnableDebug;
Logger.EnableStub.Value = configurationFileFormat.LoggingEnableStub; Logger.EnableStub.Value = configurationFileFormat.LoggingEnableStub;
Logger.EnableInfo.Value = configurationFileFormat.LoggingEnableInfo; Logger.EnableInfo.Value = configurationFileFormat.LoggingEnableInfo;

View File

@ -27,6 +27,7 @@ namespace Ryujinx.Ui
using Image = SixLabors.ImageSharp.Image; using Image = SixLabors.ImageSharp.Image;
using Key = Input.Key; using Key = Input.Key;
using Switch = HLE.Switch; using Switch = HLE.Switch;
using ScalingFilter = Graphics.GAL.ScalingFilter;
public abstract class RendererWidgetBase : DrawingArea public abstract class RendererWidgetBase : DrawingArea
{ {
@ -116,6 +117,21 @@ namespace Ryujinx.Ui
_lastCursorMoveTime = Stopwatch.GetTimestamp(); _lastCursorMoveTime = Stopwatch.GetTimestamp();
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorStateChanged; ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorStateChanged;
ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAnriAliasing;
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
}
private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e)
{
Renderer.Window.SetScalingFilter((ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
}
private void UpdateScalingFilter(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.ScalingFilter> e)
{
Renderer.Window.SetScalingFilter((ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
} }
public abstract void InitializeRenderer(); public abstract void InitializeRenderer();
@ -149,11 +165,19 @@ namespace Ryujinx.Ui
private void Renderer_Destroyed(object sender, EventArgs e) private void Renderer_Destroyed(object sender, EventArgs e)
{ {
ConfigurationState.Instance.HideCursorOnIdle.Event -= HideCursorStateChanged; ConfigurationState.Instance.HideCursorOnIdle.Event -= HideCursorStateChanged;
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAnriAliasing;
ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter;
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel;
NpadManager.Dispose(); NpadManager.Dispose();
Dispose(); Dispose();
} }
private void UpdateAnriAliasing(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.AntiAliasing> e)
{
Renderer?.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue);
}
protected override bool OnMotionNotifyEvent(EventMotion evnt) protected override bool OnMotionNotifyEvent(EventMotion evnt)
{ {
if (_hideCursorOnIdle) if (_hideCursorOnIdle)
@ -394,6 +418,10 @@ namespace Ryujinx.Ui
Device.Gpu.Renderer.Initialize(_glLogLevel); Device.Gpu.Renderer.Initialize(_glLogLevel);
Renderer.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value);
Renderer.Window.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
_gpuBackendName = GetGpuBackendName(); _gpuBackendName = GetGpuBackendName();
_gpuVendorName = GetGpuVendorName(); _gpuVendorName = GetGpuVendorName();

View File

@ -95,10 +95,14 @@ namespace Ryujinx.Ui.Windows
[GUI] Entry _graphicsShadersDumpPath; [GUI] Entry _graphicsShadersDumpPath;
[GUI] ComboBoxText _anisotropy; [GUI] ComboBoxText _anisotropy;
[GUI] ComboBoxText _aspectRatio; [GUI] ComboBoxText _aspectRatio;
[GUI] ComboBoxText _antiAliasing;
[GUI] ComboBoxText _scalingFilter;
[GUI] ComboBoxText _graphicsBackend; [GUI] ComboBoxText _graphicsBackend;
[GUI] ComboBoxText _preferredGpu; [GUI] ComboBoxText _preferredGpu;
[GUI] ComboBoxText _resScaleCombo; [GUI] ComboBoxText _resScaleCombo;
[GUI] Entry _resScaleText; [GUI] Entry _resScaleText;
[GUI] Adjustment _scalingFilterLevel;
[GUI] Scale _scalingFilterSlider;
[GUI] ToggleButton _configureController1; [GUI] ToggleButton _configureController1;
[GUI] ToggleButton _configureController2; [GUI] ToggleButton _configureController2;
[GUI] ToggleButton _configureController3; [GUI] ToggleButton _configureController3;
@ -139,6 +143,7 @@ namespace Ryujinx.Ui.Windows
_systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut; _systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut;
_resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; _resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1";
_scalingFilter.Changed += (sender, args) => _scalingFilterSlider.Visible = _scalingFilter.ActiveId == "2";
_galThreading.Changed += (sender, args) => _galThreading.Changed += (sender, args) =>
{ {
if (_galThreading.ActiveId != ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()) if (_galThreading.ActiveId != ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString())
@ -338,6 +343,8 @@ namespace Ryujinx.Ui.Windows
_anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString()); _anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString());
_aspectRatio.SetActiveId(((int)ConfigurationState.Instance.Graphics.AspectRatio.Value).ToString()); _aspectRatio.SetActiveId(((int)ConfigurationState.Instance.Graphics.AspectRatio.Value).ToString());
_graphicsBackend.SetActiveId(((int)ConfigurationState.Instance.Graphics.GraphicsBackend.Value).ToString()); _graphicsBackend.SetActiveId(((int)ConfigurationState.Instance.Graphics.GraphicsBackend.Value).ToString());
_antiAliasing.SetActiveId(((int)ConfigurationState.Instance.Graphics.AntiAliasing.Value).ToString());
_scalingFilter.SetActiveId(((int)ConfigurationState.Instance.Graphics.ScalingFilter.Value).ToString());
UpdatePreferredGpuComboBox(); UpdatePreferredGpuComboBox();
@ -345,7 +352,9 @@ namespace Ryujinx.Ui.Windows
_custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath; _custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath;
_resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString(); _resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString();
_scalingFilterLevel.Value = ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value;
_resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; _resScaleText.Visible = _resScaleCombo.ActiveId == "-1";
_scalingFilterSlider.Visible = _scalingFilter.ActiveId == "2";
_graphicsShadersDumpPath.Buffer.Text = ConfigurationState.Instance.Graphics.ShadersDumpPath; _graphicsShadersDumpPath.Buffer.Text = ConfigurationState.Instance.Graphics.ShadersDumpPath;
_fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode; _fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
_systemTimeOffset = ConfigurationState.Instance.System.SystemTimeOffset; _systemTimeOffset = ConfigurationState.Instance.System.SystemTimeOffset;
@ -605,6 +614,9 @@ namespace Ryujinx.Ui.Windows
ConfigurationState.Instance.Graphics.ResScale.Value = int.Parse(_resScaleCombo.ActiveId); ConfigurationState.Instance.Graphics.ResScale.Value = int.Parse(_resScaleCombo.ActiveId);
ConfigurationState.Instance.Graphics.ResScaleCustom.Value = resScaleCustom; ConfigurationState.Instance.Graphics.ResScaleCustom.Value = resScaleCustom;
ConfigurationState.Instance.System.AudioVolume.Value = (float)_audioVolumeSlider.Value / 100.0f; ConfigurationState.Instance.System.AudioVolume.Value = (float)_audioVolumeSlider.Value / 100.0f;
ConfigurationState.Instance.Graphics.AntiAliasing.Value = Enum.Parse<AntiAliasing>(_antiAliasing.ActiveId);
ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse<ScalingFilter>(_scalingFilter.ActiveId);
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value;
_previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value; _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value;

View File

@ -40,6 +40,13 @@
<property name="inline-completion">True</property> <property name="inline-completion">True</property>
<property name="inline-selection">True</property> <property name="inline-selection">True</property>
</object> </object>
<object class="GtkAdjustment" id="_scalingFilterLevel">
<property name="lower">0</property>
<property name="upper">101</property>
<property name="step-increment">1</property>
<property name="page-increment">5</property>
<property name="page-size">1</property>
</object>
<object class="GtkWindow" id="_settingsWin"> <object class="GtkWindow" id="_settingsWin">
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="title" translatable="yes">Ryujinx - Settings</property> <property name="title" translatable="yes">Ryujinx - Settings</property>
@ -2152,6 +2159,118 @@
<property name="position">3</property> <property name="position">3</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Applies a final effect to the game render</property>
<property name="label" translatable="yes">Post Processing Effect:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="_antiAliasing">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Applies anti-aliasing to the game render</property>
<property name="active-id">1</property>
<items>
<item id="0" translatable="yes">None</item>
<item id="1" translatable="yes">FXAA</item>
<item id="2" translatable="yes">SMAA Low</item>
<item id="3" translatable="yes">SMAA Medium</item>
<item id="4" translatable="yes">SMAA High</item>
<item id="5" translatable="yes">SMAA Ultra</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="width-request">100</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Enables Framebuffer Upscaling</property>
<property name="label" translatable="yes">Upscale: </property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="_scalingFilter">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Enables Framebuffer Upscaling</property>
<property name="active-id">1</property>
<items>
<item id="0" translatable="yes">Bilinear</item>
<item id="1" translatable="yes">Nearest</item>
<item id="2" translatable="yes">FSR</item>
</items>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScale" id="_scalingFilterSlider">
<property name="width-request">200</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-start">5</property>
<property name="adjustment">_scalingFilterLevel</property>
<property name="round-digits">1</property>
<property name="value-pos">right</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">5</property>
</packing>
</child>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
@ -2197,7 +2316,7 @@
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="padding">5</property> <property name="padding">5</property>
<property name="position">4</property> <property name="position">6</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -2246,7 +2365,7 @@
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="padding">5</property> <property name="padding">5</property>
<property name="position">5</property> <property name="position">7</property>
</packing> </packing>
</child> </child>
</object> </object>