Compare commits

...

10 Commits

Author SHA1 Message Date
gdkchan
2cdcfe46d8 Remove barrier on Intel if control flow is potentially divergent (#5044)
* Remove barrier on Intel if control flow is potentially divergent

* Shader cache version bump
2023-06-08 17:43:16 -03:00
gdkchan
fe30c03cac Implement soft float64 conversion on shaders when host has no support (#5159)
* Implement soft float64 conversion on shaders when host has no support

* Shader cache version bump

* Fix rebase error
2023-06-08 17:09:14 -03:00
Kurochi51
5813b2e354 Updater: Ignore files introduced by the user in base directory (#5092)
* Updater: Ignore files introduced by the user in base directory

* Replicate logic in Avalonia version.

* Address requested changes

* Updater: Ignore files introduced by the user in base directory

* Replicate logic in Avalonia version.

* Address requested changes

* Address requested changes

* Address requested changes

* Comment cleanup

* Address feedback

* Forgot comment, tehe
2023-06-05 14:19:17 +02:00
gdkchan
af1906ea04 Fix wrong unaligned SB state when fetching compute shaders (#5223) 2023-06-05 14:01:33 +02:00
riperiperi
68848000f7 Texture: Fix 3D texture size when totalBlocksOfGobsInZ > 1 (#5228)
* Texture: Fix 3D texture size when totalBlocksOfGobsInZ > 0

When there is a remainder when dividing depth by gobs in z, it is used to remove the unused part of the 3D texture's size. This was done to calculate correct sizes for single slice views of 3D textures.

However, this case can also apply to 3D textures with many slices, and more than one total block of gobs in z. In this case it's meant to trim off the end of the level size. Most textures won't encounter this as their size will be aligned, but UE4 games tend to use 3D textures with funny unaligned sizes.

The size offset should have been applied to the level size instead of the slice size, and it should only affect the slice size if it ends up larger.

Hopefully should fix issues with UE4 games without breaking other stuff, I don't have much time to test.

* Whoops
2023-06-05 13:33:09 +02:00
Théo Arrouye
d98da47a0f Better application grid flex (#5218) 2023-06-05 00:48:11 +00:00
Isaac Marovitz
306f7e93a0 Dont Error on Invalid Enum Values (#5169)
* Dont Error on Invalid Enum

* Use TryParse

* Log warning
2023-06-05 01:19:46 +02:00
Marco Carvalho
8954ff3af2 Replacing ZbcColorArray with Array4<uint> (#5210)
* Related "if/else if" statements should not have the same condition

* replacing ZbcColorArray with Array4<uint>

* fix alignment
2023-06-04 20:30:04 +00:00
riperiperi
d2f3adbf69 Texture: Fix layout conversion when gobs in z is used with depth = 1 (#5220)
* Texture: Fix layout conversion when gobs in z is used with depth = 1

The size calculator methods deliberately reduce the gob size of textures if they are deemed too small for it. This is required to get correct sizes when iterating mip levels of a texture.

Rendering to a slice of a 3D texture can produce a 3D texture with depth 1, but a gob size matching a much larger texture. We _can't_ "correct" this gob size, as it is intended as a slice of a larger 3D texture. Ignoring it causes layout conversion to break on read and flush.

This caused an issue in Tears of the Kingdom where the compressed 3D texture used for the gloom would always break on OpenGL, and seemingly randomly break on Vulkan. In the first case, the data is forcibly flushed to decompress the BC4 texture on the CPU to upload it as 3D, which was broken due to the incorrect layout. In the second, the data may be randomly flushed if it falls out of the cache, but it will appear correct if it's able to form copy dependencies.

This change only allows gob sizes to be reduced once per mip level. For the purpose of aligned size, it can still be reduced infinitely as our texture cache isn't properly able to handle a view being _misaligned_.

The SizeCalculator has also been changed to reduce the size of rendered depth slices to only include the exact range a single depth slice will cover. (before, the size was way too small with gobs in z reduced to 1, and too large when using the correct value)

Gobs in Y logic remains untouched, we don't support Y slices of textures so it's fine as is.

This is probably worth testing in a few games as it also affects texture size and view logic.

* Improve wording

* Maybe a bit better
2023-06-04 20:25:57 +00:00
WilliamWsyHK
d511c845b7 Check KeyboardMode in GUI (#4343)
* Update SoftwareKeyboard to send KeyboardMode to UI

* Update GTK UI to check text against KeyboardMode

* Update Ava UI to check text against KeyboardMode

* Restructure input validation

* true when text is not empty

* Add English validation text for SoftwareKeyboardMode

* Add Chinese validation text for SoftwareKeyboardMode

* Update base on feedback

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2023-06-04 05:30:24 +02:00
35 changed files with 476 additions and 96 deletions

View File

@@ -544,6 +544,9 @@
"SwkbdMinCharacters": "Must be at least {0} characters long",
"SwkbdMinRangeCharacters": "Must be {0}-{1} characters long",
"SoftwareKeyboard": "Software Keyboard",
"SoftwareKeyboardModeNumbersOnly": "Must be numbers only",
"SoftwareKeyboardModeAlphabet": "Must be alphabets only",
"SoftwareKeyboardModeASCII": "Must be ASCII text only",
"DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
"DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
"DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n",

View File

@@ -527,6 +527,9 @@
"SwkbdMinCharacters": "至少应为 {0} 个字长",
"SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长",
"SoftwareKeyboard": "软件键盘",
"SoftwareKeyboardModeNumbersOnly": "只接受数字",
"SoftwareKeyboardModeAlphabet": "只接受英文字母",
"SoftwareKeyboardModeASCII": "只接受 ASCII 符号",
"DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家并满足以下要求:\n\n手柄类型{1}\n\n玩家类型{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。",
"DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家并满足以下要求:\n\n手柄类型{1}\n\n玩家类型{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。",
"DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式",

View File

@@ -527,6 +527,9 @@
"SwkbdMinCharacters": "至少應為 {0} 個字長",
"SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長",
"SoftwareKeyboard": "軟體鍵盤",
"SoftwareKeyboardModeNumbersOnly": "只接受數字",
"SoftwareKeyboardModeAlphabet": "只接受英文字母",
"SoftwareKeyboardModeASCII": "只接受 ASCII 符號",
"DialogControllerAppletMessagePlayerRange": "本遊戲需要 {0} 個玩家持有:\n\n類型{1}\n\n玩家{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。",
"DialogControllerAppletMessage": "本遊戲需要剛好 {0} 個玩家持有:\n\n類型{1}\n\n玩家{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。",
"DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n",

View File

@@ -740,6 +740,18 @@ namespace Ryujinx.Modules
{
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
// Determine and exclude user files only when the updater is running, not when cleaning old files
if (_running)
{
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
var oldFiles = Directory.EnumerateFiles(HomeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var newFiles = Directory.EnumerateFiles(UpdatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(HomeDir, filename));
// Remove user files from the paths in files.
files = files.Except(userFiles);
}
if (OperatingSystem.IsWindows())
{
foreach (string dir in WindowsDependencyDirs)

View File

@@ -9,14 +9,17 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Controls
{
internal partial class SwkbdAppletDialog : UserControl
{
private Predicate<int> _checkLength;
private Predicate<int> _checkLength = _ => true;
private Predicate<string> _checkInput = _ => true;
private int _inputMax;
private int _inputMin;
private string _placeholder;
@@ -35,8 +38,6 @@ namespace Ryujinx.Ava.UI.Controls
Input.Watermark = _placeholder;
Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true);
SetInputLengthValidation(0, int.MaxValue); // Disable by default.
}
public SwkbdAppletDialog()
@@ -67,6 +68,7 @@ namespace Ryujinx.Ava.UI.Controls
string input = string.Empty;
content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
content.SetInputValidation(args.KeyboardMode);
content._host = contentDialog;
contentDialog.Title = title;
@@ -91,6 +93,12 @@ namespace Ryujinx.Ava.UI.Controls
return (result, input);
}
private void ApplyValidationInfo(string text)
{
Error.IsVisible = !string.IsNullOrEmpty(text);
Error.Text = text;
}
public void SetInputLengthValidation(int min, int max)
{
_inputMin = Math.Min(min, max);
@@ -99,6 +107,8 @@ namespace Ryujinx.Ava.UI.Controls
Error.IsVisible = false;
Error.FontStyle = FontStyle.Italic;
string validationInfoText = "";
if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable.
{
Error.IsVisible = false;
@@ -107,21 +117,48 @@ namespace Ryujinx.Ava.UI.Controls
}
else if (_inputMin > 0 && _inputMax == int.MaxValue)
{
Error.IsVisible = true;
Error.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin);
validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin);
_checkLength = length => _inputMin <= length;
}
else
{
Error.IsVisible = true;
Error.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax);
validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax);
_checkLength = length => _inputMin <= length && length <= _inputMax;
}
ApplyValidationInfo(validationInfoText);
Message_TextInput(this, new TextInputEventArgs());
}
private void SetInputValidation(KeyboardMode mode)
{
string validationInfoText = Error.Text;
string localeText;
switch (mode)
{
case KeyboardMode.NumbersOnly:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumbersOnly);
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
_checkInput = text => text.All(char.IsDigit);
break;
case KeyboardMode.Alphabet:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet);
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
_checkInput = text => text.All(char.IsAsciiLetter);
break;
case KeyboardMode.ASCII:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeASCII);
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
_checkInput = text => text.All(char.IsAscii);
break;
default:
_checkInput = _ => true;
break;
}
ApplyValidationInfo(validationInfoText);
Message_TextInput(this, new TextInputEventArgs());
}
@@ -129,7 +166,7 @@ namespace Ryujinx.Ava.UI.Controls
{
if (_host != null)
{
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length);
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message);
}
}
@@ -141,7 +178,7 @@ namespace Ryujinx.Ava.UI.Controls
}
else
{
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length);
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message);
}
}
}

View File

@@ -32,10 +32,10 @@
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<flex:FlexPanel
HorizontalAlignment="Stretch"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
AlignContent="FlexStart"
JustifyContent="Center" />
JustifyContent="FlexStart" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>

View File

@@ -1,4 +1,5 @@
#nullable enable
using Ryujinx.Common.Logging;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -18,12 +19,14 @@ namespace Ryujinx.Common.Utilities
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var enumValue = reader.GetString();
if (string.IsNullOrEmpty(enumValue))
if (Enum.TryParse(enumValue, out TEnum value))
{
return default;
return value;
}
return Enum.Parse<TEnum>(enumValue);
Logger.Warning?.Print(LogClass.Configuration, $"Failed to parse enum value \"{enumValue}\" for {typeof(TEnum)}, using default \"{default(TEnum)}\"");
return default;
}
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
@@ -31,4 +34,4 @@ namespace Ryujinx.Common.Utilities
writer.WriteStringValue(value.ToString());
}
}
}
}

View File

@@ -34,6 +34,8 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsCubemapView;
public readonly bool SupportsNonConstantTextureOffset;
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsShaderFloat64;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsViewportIndexVertexTessellation;
public readonly bool SupportsViewportMask;
@@ -81,6 +83,8 @@ namespace Ryujinx.Graphics.GAL
bool supportsCubemapView,
bool supportsNonConstantTextureOffset,
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
bool supportsShaderFloat64,
bool supportsTextureShadowLod,
bool supportsViewportIndexVertexTessellation,
bool supportsViewportMask,
@@ -124,6 +128,8 @@ namespace Ryujinx.Graphics.GAL
SupportsCubemapView = supportsCubemapView;
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsShaderFloat64 = supportsShaderFloat64;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
SupportsViewportMask = supportsViewportMask;

View File

@@ -151,8 +151,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
ShaderProgramInfo info = cs.Shaders[0].Info;
bool hasUnaligned = _channel.BufferManager.HasUnalignedStorageBuffers;
for (int index = 0; index < info.SBuffers.Count; index++)
{
BufferDescriptor sb = info.SBuffers[index];
@@ -177,9 +175,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags);
}
if ((_channel.BufferManager.HasUnalignedStorageBuffers) != hasUnaligned)
if (_channel.BufferManager.HasUnalignedStorageBuffers != computeState.HasUnalignedStorageBuffer)
{
// Refetch the shader, as assumptions about storage buffer alignment have changed.
computeState = new GpuChannelComputeState(
qmd.CtaThreadDimension0,
qmd.CtaThreadDimension1,
qmd.CtaThreadDimension2,
localMemorySize,
sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers);
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);

View File

@@ -541,7 +541,8 @@ namespace Ryujinx.Graphics.Gpu.Image
depth,
lhs.FormatInfo.BlockHeight,
lhs.GobBlocksInY,
lhs.GobBlocksInZ);
lhs.GobBlocksInZ,
level);
return gobBlocksInY == rhs.GobBlocksInY &&
gobBlocksInZ == rhs.GobBlocksInZ;
@@ -587,7 +588,8 @@ namespace Ryujinx.Graphics.Gpu.Image
lhsDepth,
lhs.FormatInfo.BlockHeight,
lhs.GobBlocksInY,
lhs.GobBlocksInZ);
lhs.GobBlocksInZ,
lhsLevel);
int rhsHeight = Math.Max(1, rhs.Height >> rhsLevel);
int rhsDepth = Math.Max(1, rhs.GetDepth() >> rhsLevel);
@@ -597,7 +599,8 @@ namespace Ryujinx.Graphics.Gpu.Image
rhsDepth,
rhs.FormatInfo.BlockHeight,
rhs.GobBlocksInY,
rhs.GobBlocksInZ);
rhs.GobBlocksInZ,
rhsLevel);
return lhsGobBlocksInY == rhsGobBlocksInY &&
lhsGobBlocksInZ == rhsGobBlocksInZ;

View File

@@ -484,7 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Image
depthOrLayers = Math.Max(1, depthOrLayers >> minLod);
}
(gobBlocksInY, gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(height, depth, formatInfo.BlockHeight, gobBlocksInY, gobBlocksInZ);
(gobBlocksInY, gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(height, depth, formatInfo.BlockHeight, gobBlocksInY, gobBlocksInZ, minLod);
}
levels = (maxLod - minLod) + 1;

View File

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

View File

@@ -141,6 +141,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
public bool QueryHostSupportsShaderBarrierDivergence() => _context.Capabilities.SupportsShaderBarrierDivergence;
public bool QueryHostSupportsShaderFloat64() => _context.Capabilities.SupportsShaderFloat64;
public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat;
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;

View File

@@ -127,6 +127,7 @@ namespace Ryujinx.Graphics.OpenGL
public Capabilities GetCapabilities()
{
bool intelWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows;
bool intelUnix = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelUnix;
bool amdWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows;
return new Capabilities(
@@ -158,6 +159,8 @@ namespace Ryujinx.Graphics.OpenGL
supportsCubemapView: true,
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
supportsShaderFloat64: true,
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
supportsViewportMask: HwCapabilities.SupportsViewportArray2,

View File

@@ -28,18 +28,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
for (int i = 1; i < info.Functions.Count; i++)
{
PrintFunction(context, info, info.Functions[i]);
PrintFunction(context, info.Functions[i]);
context.AppendLine();
}
}
PrintFunction(context, info, info.Functions[0], MainFunctionName);
PrintFunction(context, info.Functions[0], MainFunctionName);
return context.GetCode();
}
private static void PrintFunction(CodeGenContext context, StructuredProgramInfo info, StructuredFunction function, string funcName = null)
private static void PrintFunction(CodeGenContext context, StructuredFunction function, string funcName = null)
{
context.CurrentFunction = function;
@@ -48,7 +48,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
Declarations.DeclareLocals(context, function);
PrintBlock(context, function.MainBlock);
PrintBlock(context, function.MainBlock, funcName == MainFunctionName);
context.LeaveScope();
}
@@ -72,7 +72,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return $"{Declarations.GetVarTypeName(context, function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})";
}
private static void PrintBlock(CodeGenContext context, AstBlock block)
private static void PrintBlock(CodeGenContext context, AstBlock block, bool isMainFunction)
{
AstBlockVisitor visitor = new AstBlockVisitor(block);
@@ -112,10 +112,32 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
};
bool supportsBarrierDivergence = context.Config.GpuAccessor.QueryHostSupportsShaderBarrierDivergence();
bool mayHaveReturned = false;
foreach (IAstNode node in visitor.Visit())
{
if (node is AstOperation operation)
{
if (!supportsBarrierDivergence)
{
if (operation.Inst == IntermediateRepresentation.Instruction.Barrier)
{
// Barrier on divergent control flow paths may cause the GPU to hang,
// so skip emitting the barrier for those cases.
if (visitor.Block.Type != AstBlockType.Main || mayHaveReturned || !isMainFunction)
{
context.Config.GpuAccessor.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
continue;
}
}
else if (operation.Inst == IntermediateRepresentation.Instruction.Return)
{
mayHaveReturned = true;
}
}
string expr = InstGen.GetExpression(context, operation);
if (expr != null)

View File

@@ -76,6 +76,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public SpirvDelegates Delegates { get; }
public bool IsMainFunction { get; private set; }
public bool MayHaveReturned { get; set; }
public CodeGenContext(
StructuredProgramInfo info,
ShaderConfig config,
@@ -108,8 +111,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Delegates = new SpirvDelegates(this);
}
public void StartFunction()
public void StartFunction(bool isMainFunction)
{
IsMainFunction = isMainFunction;
MayHaveReturned = false;
_locals.Clear();
_localForArgs.Clear();
_funcArgs.Clear();

View File

@@ -242,6 +242,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateBarrier(CodeGenContext context, AstOperation operation)
{
// Barrier on divergent control flow paths may cause the GPU to hang,
// so skip emitting the barrier for those cases.
if (!context.Config.GpuAccessor.QueryHostSupportsShaderBarrierDivergence() &&
(context.CurrentBlock.Type != AstBlockType.Main || context.MayHaveReturned || !context.IsMainFunction))
{
context.Config.GpuAccessor.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
return OperationResult.Invalid;
}
context.ControlBarrier(
context.Constant(context.TypeU32(), Scope.Workgroup),
context.Constant(context.TypeU32(), Scope.Workgroup),
@@ -1092,6 +1102,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateReturn(CodeGenContext context, AstOperation operation)
{
context.MayHaveReturned = true;
if (operation.SourcesCount != 0)
{
context.ReturnValue(context.Get(context.CurrentFunction.ReturnType, operation.GetSource(0)));

View File

@@ -148,7 +148,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.CurrentFunction = function;
context.AddFunction(spvFunc);
context.StartFunction();
context.StartFunction(isMainFunction: funcIndex == 0);
Declarations.DeclareParameters(context, function);

View File

@@ -331,6 +331,24 @@ namespace Ryujinx.Graphics.Shader
return true;
}
/// <summary>
/// Queries host GPU shader support for barrier instructions on divergent control flow paths.
/// </summary>
/// <returns>True if the GPU supports barriers on divergent control flow paths, false otherwise</returns>
bool QueryHostSupportsShaderBarrierDivergence()
{
return true;
}
/// <summary>
/// Queries host GPU support for 64-bit floating point (double precision) operations on the shader.
/// </summary>
/// <returns>True if the GPU and driver supports double operations, false otherwise</returns>
bool QueryHostSupportsShaderFloat64()
{
return true;
}
/// <summary>
/// Queries host GPU support for signed normalized buffer texture formats.
/// </summary>

View File

@@ -255,5 +255,35 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
_sources = new Operand[] { source };
}
public void TurnDoubleIntoFloat()
{
if ((Inst & ~Instruction.Mask) == Instruction.FP64)
{
Inst = (Inst & Instruction.Mask) | Instruction.FP32;
}
else
{
switch (Inst)
{
case Instruction.ConvertFP32ToFP64:
case Instruction.ConvertFP64ToFP32:
Inst = Instruction.Copy;
break;
case Instruction.ConvertFP64ToS32:
Inst = Instruction.ConvertFP32ToS32;
break;
case Instruction.ConvertFP64ToU32:
Inst = Instruction.ConvertFP32ToU32;
break;
case Instruction.ConvertS32ToFP64:
Inst = Instruction.ConvertS32ToFP32;
break;
case Instruction.ConvertU32ToFP64:
Inst = Instruction.ConvertU32ToFP32;
break;
}
}
}
}
}

View File

@@ -45,12 +45,101 @@ namespace Ryujinx.Graphics.Shader.Translation
{
return functionName switch
{
HelperFunctionName.ConvertDoubleToFloat => GenerateConvertDoubleToFloatFunction(),
HelperFunctionName.ConvertFloatToDouble => GenerateConvertFloatToDoubleFunction(),
HelperFunctionName.TexelFetchScale => GenerateTexelFetchScaleFunction(),
HelperFunctionName.TextureSizeUnscale => GenerateTextureSizeUnscaleFunction(),
_ => throw new ArgumentException($"Invalid function name {functionName}")
};
}
private Function GenerateConvertDoubleToFloatFunction()
{
EmitterContext context = new EmitterContext();
Operand valueLow = Argument(0);
Operand valueHigh = Argument(1);
Operand mantissaLow = context.BitwiseAnd(valueLow, Const(((1 << 22) - 1)));
Operand mantissa = context.ShiftRightU32(valueLow, Const(22));
mantissa = context.BitwiseOr(mantissa, context.ShiftLeft(context.BitwiseAnd(valueHigh, Const(0xfffff)), Const(10)));
mantissa = context.BitwiseOr(mantissa, context.ConditionalSelect(mantissaLow, Const(1), Const(0)));
Operand exp = context.BitwiseAnd(context.ShiftRightU32(valueHigh, Const(20)), Const(0x7ff));
Operand sign = context.ShiftRightS32(valueHigh, Const(31));
Operand resultSign = context.ShiftLeft(sign, Const(31));
Operand notZero = context.BitwiseOr(mantissa, exp);
Operand lblNotZero = Label();
context.BranchIfTrue(lblNotZero, notZero);
context.Return(resultSign);
context.MarkLabel(lblNotZero);
Operand notNaNOrInf = context.ICompareNotEqual(exp, Const(0x7ff));
mantissa = context.BitwiseOr(mantissa, Const(0x40000000));
exp = context.ISubtract(exp, Const(0x381));
// Note: Overflow cases are not handled here and might produce incorrect results.
Operand roundBits = context.BitwiseAnd(mantissa, Const(0x7f));
Operand roundBitsXor64 = context.BitwiseExclusiveOr(roundBits, Const(0x40));
mantissa = context.ShiftRightU32(context.IAdd(mantissa, Const(0x40)), Const(7));
mantissa = context.BitwiseAnd(mantissa, context.ConditionalSelect(roundBitsXor64, Const(~0), Const(~1)));
exp = context.ConditionalSelect(mantissa, exp, Const(0));
exp = context.ConditionalSelect(notNaNOrInf, exp, Const(0xff));
Operand result = context.IAdd(context.IAdd(mantissa, context.ShiftLeft(exp, Const(23))), resultSign);
context.Return(result);
return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ConvertDoubleToFloat", true, 2, 0);
}
private Function GenerateConvertFloatToDoubleFunction()
{
EmitterContext context = new EmitterContext();
Operand value = Argument(0);
Operand mantissa = context.BitwiseAnd(value, Const(0x7fffff));
Operand exp = context.BitwiseAnd(context.ShiftRightU32(value, Const(23)), Const(0xff));
Operand sign = context.ShiftRightS32(value, Const(31));
Operand notNaNOrInf = context.ICompareNotEqual(exp, Const(0xff));
Operand expNotZero = context.ICompareNotEqual(exp, Const(0));
Operand notDenorm = context.BitwiseOr(expNotZero, context.ICompareEqual(mantissa, Const(0)));
exp = context.IAdd(exp, Const(0x380));
Operand shiftDist = context.ISubtract(Const(32), context.FindMSBU32(mantissa));
Operand normExp = context.ISubtract(context.ISubtract(Const(1), shiftDist), Const(1));
Operand normMant = context.ShiftLeft(mantissa, shiftDist);
exp = context.ConditionalSelect(notNaNOrInf, exp, Const(0x7ff));
exp = context.ConditionalSelect(notDenorm, exp, normExp);
mantissa = context.ConditionalSelect(expNotZero, mantissa, normMant);
Operand resultLow = context.ShiftLeft(mantissa, Const(29));
Operand resultHigh = context.ShiftRightU32(mantissa, Const(3));
resultHigh = context.IAdd(resultHigh, context.ShiftLeft(exp, Const(20)));
resultHigh = context.IAdd(resultHigh, context.ShiftLeft(sign, Const(31)));
context.Copy(Argument(1), resultLow);
context.Copy(Argument(2), resultHigh);
context.Return();
return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ConvertFloatToDouble", false, 1, 2);
}
private Function GenerateTexelFetchScaleFunction()
{
EmitterContext context = new EmitterContext();

View File

@@ -1,10 +1,9 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation
{
enum HelperFunctionName
{
ConvertDoubleToFloat,
ConvertFloatToDouble,
TexelFetchScale,
TextureSizeUnscale
}

View File

@@ -0,0 +1,70 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class DoubleToFloat
{
public static void RunPass(HelperFunctionManager hfm, BasicBlock block)
{
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
{
if (node.Value is not Operation operation)
{
continue;
}
node = InsertSoftFloat64(hfm, node);
}
}
private static LinkedListNode<INode> InsertSoftFloat64(HelperFunctionManager hfm, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
if (operation.Inst == Instruction.PackDouble2x32)
{
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.ConvertDoubleToFloat);
Operand[] callArgs = new Operand[] { Const(functionId), operation.GetSource(0), operation.GetSource(1) };
Operand floatValue = operation.Dest;
operation.Dest = null;
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, floatValue, callArgs));
Utils.DeleteNode(node, operation);
return newNode;
}
else if (operation.Inst == Instruction.UnpackDouble2x32)
{
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.ConvertFloatToDouble);
// TODO: Allow UnpackDouble2x32 to produce two outputs and get rid of "operation.Index".
Operand resultLow = operation.Index == 0 ? operation.Dest : Local();
Operand resultHigh = operation.Index == 1 ? operation.Dest : Local();
operation.Dest = null;
Operand[] callArgs = new Operand[] { Const(functionId), operation.GetSource(0), resultLow, resultHigh };
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, (Operand)null, callArgs));
Utils.DeleteNode(node, operation);
return newNode;
}
else
{
operation.TurnDoubleIntoFloat();
return node;
}
}
}
}

View File

@@ -11,8 +11,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
RunOptimizationPasses(blocks, config);
// TODO: Some of those are not optimizations and shouldn't be here.
GlobalToStorage.RunPass(hfm, blocks, config);
bool hostSupportsShaderFloat64 = config.GpuAccessor.QueryHostSupportsShaderFloat64();
// Those passes are looking for specific patterns and only needs to run once.
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
{
@@ -24,6 +28,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
EliminateMultiplyByFragmentCoordW(blocks[blkIndex]);
}
// If the host does not support double operations, we need to turn them into float operations.
if (!hostSupportsShaderFloat64)
{
DoubleToFloat.RunPass(hfm, blocks[blkIndex]);
}
}
// Run optimizations one last time to remove any code that is now optimizable after above passes.

View File

@@ -143,7 +143,7 @@ namespace Ryujinx.Graphics.Texture
mipGobBlocksInY >>= 1;
}
while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
{
mipGobBlocksInZ >>= 1;
}
@@ -407,7 +407,7 @@ namespace Ryujinx.Graphics.Texture
mipGobBlocksInY >>= 1;
}
while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
{
mipGobBlocksInZ >>= 1;
}

View File

@@ -36,7 +36,7 @@ namespace Ryujinx.Graphics.Texture
int gobBlocksInTileX,
int gpuLayerSize = 0)
{
bool is3D = depth > 1;
bool is3D = depth > 1 || gobBlocksInZ > 1;
int layerSize = 0;
@@ -67,7 +67,7 @@ namespace Ryujinx.Graphics.Texture
mipGobBlocksInY >>= 1;
}
while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
{
mipGobBlocksInZ >>= 1;
}
@@ -88,6 +88,10 @@ namespace Ryujinx.Graphics.Texture
int robSize = widthInGobs * mipGobBlocksInY * mipGobBlocksInZ * GobSize;
mipOffsets[level] = layerSize;
sliceSizes[level] = totalBlocksOfGobsInY * robSize;
levelSizes[level] = totalBlocksOfGobsInZ * sliceSizes[level];
if (is3D)
{
int gobSize = mipGobBlocksInY * GobSize;
@@ -105,11 +109,22 @@ namespace Ryujinx.Graphics.Texture
allOffsets[z + depthLevelOffset] = baseOffset + zLow * gobSize + zHigh * sliceSize;
}
}
mipOffsets[level] = layerSize;
sliceSizes[level] = totalBlocksOfGobsInY * robSize;
levelSizes[level] = totalBlocksOfGobsInZ * sliceSizes[level];
int gobRemainderZ = d % mipGobBlocksInZ;
if (gobRemainderZ != 0 && level == levels - 1)
{
// The slice only covers up to the end of this slice's depth, rather than the full aligned size.
// Avoids size being too large on partial views of 3d textures.
levelSizes[level] -= gobSize * (mipGobBlocksInZ - gobRemainderZ);
if (sliceSizes[level] > levelSizes[level])
{
sliceSizes[level] = levelSizes[level];
}
}
}
layerSize += levelSizes[level];
@@ -267,7 +282,8 @@ namespace Ryujinx.Graphics.Texture
int depth,
int blockHeight,
int gobBlocksInY,
int gobBlocksInZ)
int gobBlocksInZ,
int level = int.MaxValue)
{
height = BitUtils.DivRoundUp(height, blockHeight);
@@ -276,7 +292,7 @@ namespace Ryujinx.Graphics.Texture
gobBlocksInY >>= 1;
}
while (depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1)
while (level-- > 0 && depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1)
{
gobBlocksInZ >>= 1;
}

View File

@@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsFragmentShaderInterlock;
public readonly bool SupportsGeometryShaderPassthrough;
public readonly bool SupportsSubgroupSizeControl;
public readonly bool SupportsShaderFloat64;
public readonly bool SupportsShaderInt8;
public readonly bool SupportsShaderStencilExport;
public readonly bool SupportsShaderStorageImageMultisample;
@@ -63,6 +64,7 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsFragmentShaderInterlock,
bool supportsGeometryShaderPassthrough,
bool supportsSubgroupSizeControl,
bool supportsShaderFloat64,
bool supportsShaderInt8,
bool supportsShaderStencilExport,
bool supportsShaderStorageImageMultisample,
@@ -99,6 +101,7 @@ namespace Ryujinx.Graphics.Vulkan
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
SupportsSubgroupSizeControl = supportsSubgroupSizeControl;
SupportsShaderFloat64 = supportsShaderFloat64;
SupportsShaderInt8 = supportsShaderInt8;
SupportsShaderStencilExport = supportsShaderStencilExport;
SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;

View File

@@ -306,6 +306,7 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"),
_physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough"),
supportsSubgroupSizeControl,
features2.Features.ShaderFloat64,
featuresShaderInt8.ShaderInt8,
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
features2.Features.ShaderStorageImageMultisample,
@@ -594,6 +595,8 @@ namespace Ryujinx.Graphics.Vulkan
supportsCubemapView: !IsAmdGcn,
supportsNonConstantTextureOffset: false,
supportsShaderBallot: false,
supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
supportsShaderFloat64: Capabilities.SupportsShaderFloat64,
supportsTextureShadowLod: false,
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,
supportsViewportMask: Capabilities.SupportsViewportArray2,

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Identifies the variant of keyboard displayed on screen.
/// </summary>
enum KeyboardMode : uint
public enum KeyboardMode : uint
{
/// <summary>
/// A full alpha-numeric keyboard.

View File

@@ -209,6 +209,7 @@ namespace Ryujinx.HLE.HOS.Applets
// Call the configured GUI handler to get user's input.
var args = new SoftwareKeyboardUiArgs
{
KeyboardMode = _keyboardForegroundConfig.Mode,
HeaderText = StripUnicodeControlCodes(_keyboardForegroundConfig.HeaderText),
SubtitleText = StripUnicodeControlCodes(_keyboardForegroundConfig.SubtitleText),
GuideText = StripUnicodeControlCodes(_keyboardForegroundConfig.GuideText),

View File

@@ -1,7 +1,10 @@
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
namespace Ryujinx.HLE.HOS.Applets
{
public struct SoftwareKeyboardUiArgs
{
public KeyboardMode KeyboardMode;
public string HeaderText;
public string SubtitleText;
public string InitialText;

View File

@@ -1,49 +1,16 @@
using System;
using Ryujinx.Common.Memory;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct ZbcColorArray
{
private uint element0;
private uint element1;
private uint element2;
private uint element3;
public uint this[int index]
{
get
{
if (index == 0)
{
return element0;
}
else if (index == 1)
{
return element1;
}
else if (index == 2)
{
return element2;
}
else if (index == 2)
{
return element3;
}
throw new IndexOutOfRangeException();
}
}
}
[StructLayout(LayoutKind.Sequential)]
struct ZbcSetTableArguments
{
public ZbcColorArray ColorDs;
public ZbcColorArray ColorL2;
public uint Depth;
public uint Format;
public uint Type;
public Array4<uint> ColorDs;
public Array4<uint> ColorL2;
public uint Depth;
public uint Format;
public uint Type;
}
}

View File

@@ -565,6 +565,18 @@ namespace Ryujinx.Modules
{
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
// Determine and exclude user files only when the updater is running, not when cleaning old files
if (Running)
{
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
var oldFiles = Directory.EnumerateFiles(HomeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var newFiles = Directory.EnumerateFiles(UpdatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(HomeDir, filename));
// Remove user files from the paths in files.
files = files.Except(userFiles);
}
if (OperatingSystem.IsWindows())
{
foreach (string dir in WindowsDependencyDirs)

View File

@@ -106,6 +106,7 @@ namespace Ryujinx.Ui.Applet
swkbdDialog.OkButton.Label = args.SubmitText;
swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
swkbdDialog.SetInputValidation(args.KeyboardMode);
if (swkbdDialog.Run() == (int)ResponseType.Ok)
{

View File

@@ -1,5 +1,7 @@
using Gtk;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using System;
using System.Linq;
namespace Ryujinx.Ui.Applet
{
@@ -7,8 +9,12 @@ namespace Ryujinx.Ui.Applet
{
private int _inputMin;
private int _inputMax;
private KeyboardMode _mode;
private Predicate<int> _checkLength;
private string _validationInfoText = "";
private Predicate<int> _checkLength = _ => true;
private Predicate<string> _checkInput = _ => true;
private readonly Label _validationInfo;
@@ -38,8 +44,12 @@ namespace Ryujinx.Ui.Applet
((Box)MessageArea).PackEnd(_validationInfo, true, true, 0);
((Box)MessageArea).PackEnd(InputEntry, true, true, 4);
}
SetInputLengthValidation(0, int.MaxValue); // Disable by default.
private void ApplyValidationInfo()
{
_validationInfo.Visible = !string.IsNullOrEmpty(_validationInfoText);
_validationInfo.Markup = _validationInfoText;
}
public void SetInputLengthValidation(int min, int max)
@@ -53,23 +63,49 @@ namespace Ryujinx.Ui.Applet
{
_validationInfo.Visible = false;
_checkLength = (length) => true;
_checkLength = _ => true;
}
else if (_inputMin > 0 && _inputMax == int.MaxValue)
{
_validationInfo.Visible = true;
_validationInfo.Markup = $"<i>Must be at least {_inputMin} characters long</i>";
_validationInfoText = $"<i>Must be at least {_inputMin} characters long.</i> ";
_checkLength = (length) => _inputMin <= length;
_checkLength = length => _inputMin <= length;
}
else
{
_validationInfo.Visible = true;
_validationInfo.Markup = $"<i>Must be {_inputMin}-{_inputMax} characters long</i>";
_validationInfoText = $"<i>Must be {_inputMin}-{_inputMax} characters long.</i> ";
_checkLength = (length) => _inputMin <= length && length <= _inputMax;
_checkLength = length => _inputMin <= length && length <= _inputMax;
}
ApplyValidationInfo();
OnInputChanged(this, EventArgs.Empty);
}
public void SetInputValidation(KeyboardMode mode)
{
_mode = mode;
switch (mode)
{
case KeyboardMode.NumbersOnly:
_validationInfoText += "<i>Must be numbers only.</i>";
_checkInput = text => text.All(char.IsDigit);
break;
case KeyboardMode.Alphabet:
_validationInfoText += "<i>Must be alphabets only.</i>";
_checkInput = text => text.All(char.IsAsciiLetter);
break;
case KeyboardMode.ASCII:
_validationInfoText += "<i>Must be ASCII text only.</i>";
_checkInput = text => text.All(char.IsAscii);
break;
default:
_checkInput = _ => true;
break;
}
ApplyValidationInfo();
OnInputChanged(this, EventArgs.Empty);
}
@@ -83,7 +119,7 @@ namespace Ryujinx.Ui.Applet
private void OnInputChanged(object sender, EventArgs e)
{
OkButton.Sensitive = _checkLength(InputEntry.Text.Length);
OkButton.Sensitive = _checkLength(InputEntry.Text.Length) && _checkInput(InputEntry.Text);
}
}
}