diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c6dc724c8..d379437b2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -27,7 +27,7 @@ jobs:
build:
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
runs-on: ${{ matrix.os }}
- timeout-minutes: 35
+ timeout-minutes: 45
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
@@ -110,7 +110,7 @@ jobs:
build_macos:
name: macOS Universal (${{ matrix.configuration }})
runs-on: ubuntu-latest
- timeout-minutes: 35
+ timeout-minutes: 45
strategy:
matrix:
configuration: [ Debug, Release ]
diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml
index 04b917c34..d4380e05f 100644
--- a/.github/workflows/flatpak.yml
+++ b/.github/workflows/flatpak.yml
@@ -12,7 +12,7 @@ concurrency: flatpak-release
jobs:
release:
- timeout-minutes: 35
+ timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
runs-on: ubuntu-latest
env:
diff --git a/.github/workflows/nightly_pr_comment.yml b/.github/workflows/nightly_pr_comment.yml
index 9ddd458c6..deabae670 100644
--- a/.github/workflows/nightly_pr_comment.yml
+++ b/.github/workflows/nightly_pr_comment.yml
@@ -7,7 +7,7 @@ jobs:
pr_comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
- timeout-minutes: 35
+ timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
steps:
- uses: actions/github-script@v6
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a32033190..98ba34822 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -33,7 +33,7 @@ jobs:
shell: bash
- name: Create tag
- uses: actions/github-script@v5
+ uses: actions/github-script@v6
with:
script: |
github.rest.git.createRef({
@@ -46,7 +46,7 @@ jobs:
release:
name: Release ${{ matrix.OS_NAME }}
runs-on: ${{ matrix.os }}
- timeout-minutes: 35
+ timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
@@ -144,7 +144,7 @@ jobs:
macos_release:
name: Release MacOS universal
runs-on: ubuntu-latest
- timeout-minutes: 35
+ timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
steps:
- uses: actions/checkout@v3
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 0bbe81406..5054689ca 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,11 +3,11 @@
true
-
-
-
-
-
+
+
+
+
+
diff --git a/distribution/linux/Ryujinx.sh b/distribution/linux/Ryujinx.sh
index 91fb467df..a80cdcaec 100644
--- a/distribution/linux/Ryujinx.sh
+++ b/distribution/linux/Ryujinx.sh
@@ -11,4 +11,10 @@ if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
RYUJINX_BIN="Ryujinx.Headless.SDL2"
fi
-env DOTNET_EnableAlternateStackCheck=1 "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
+COMMAND="env DOTNET_EnableAlternateStackCheck=1"
+
+if command -v gamemoderun > /dev/null 2>&1; then
+ COMMAND="$COMMAND gamemoderun"
+fi
+
+$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
diff --git a/distribution/macos/updater.sh b/distribution/macos/updater.sh
index 0854d4347..4d7dcdf23 100755
--- a/distribution/macos/updater.sh
+++ b/distribution/macos/updater.sh
@@ -25,14 +25,27 @@ error_handler() {
exit 1
}
-# Wait for Ryujinx to exit
-# NOTE: in case no fds are open, lsof could be returning with a process still living.
-# We wait 1s and assume the process stopped after that
-lsof -p $APP_PID +r 1 &>/dev/null
-sleep 1
-
trap 'error_handler ${LINENO}' ERR
+# Wait for Ryujinx to exit.
+# If the main process is still acitve, we wait for 1 second and check it again.
+# After the fifth time checking, this script exits with status 1.
+
+attempt=0
+while true; do
+ if lsof -p $APP_PID +r 1 &>/dev/null || ps -p "$APP_PID" &>/dev/null; then
+ if [ "$attempt" -eq 4 ]; then
+ exit 1
+ fi
+ sleep 1
+ else
+ break
+ fi
+ (( attempt++ ))
+done
+
+sleep 1
+
# Now replace and reopen.
rm -rf "$INSTALL_DIRECTORY"
mv "$NEW_APP_DIRECTORY" "$INSTALL_DIRECTORY"
diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
index d3a73cfcb..7bfff5f9b 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
+++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
@@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Backends.SDL2
}
else
{
- _supportSurroundConfiguration = spec.channels == 6;
+ _supportSurroundConfiguration = spec.channels >= 6;
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
index 53570243d..1ad7b8590 100644
--- a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
@@ -31,7 +31,6 @@ namespace Ryujinx.Audio.Renderer.Server
private AudioRendererRenderingDevice _renderingDevice;
private AudioRendererExecutionMode _executionMode;
private IWritableEvent _systemEvent;
- private ManualResetEvent _terminationEvent;
private MemoryPoolState _dspMemoryPoolState;
private VoiceContext _voiceContext;
private MixContext _mixContext;
@@ -83,7 +82,6 @@ namespace Ryujinx.Audio.Renderer.Server
public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent)
{
_manager = manager;
- _terminationEvent = new ManualResetEvent(false);
_dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp);
_voiceContext = new VoiceContext();
_mixContext = new MixContext();
@@ -387,11 +385,6 @@ namespace Ryujinx.Audio.Renderer.Server
_isActive = false;
}
- if (_executionMode == AudioRendererExecutionMode.Auto)
- {
- _terminationEvent.WaitOne();
- }
-
Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}");
}
@@ -668,8 +661,6 @@ namespace Ryujinx.Audio.Renderer.Server
{
if (_isActive)
{
- _terminationEvent.Reset();
-
if (!_manager.Processor.HasRemainingCommands(_sessionId))
{
GenerateCommandList(out CommandList commands);
@@ -686,10 +677,6 @@ namespace Ryujinx.Audio.Renderer.Server
_isDspRunningBehind = true;
}
}
- else
- {
- _terminationEvent.Set();
- }
}
}
@@ -857,7 +844,6 @@ namespace Ryujinx.Audio.Renderer.Server
}
_manager.Unregister(this);
- _terminationEvent.Dispose();
_workBufferMemoryPin.Dispose();
if (MemoryManager is IRefCounted rc)
diff --git a/src/Ryujinx.Ava/AppHost.cs b/src/Ryujinx.Ava/AppHost.cs
index f3e90ef17..349b64b41 100644
--- a/src/Ryujinx.Ava/AppHost.cs
+++ b/src/Ryujinx.Ava/AppHost.cs
@@ -270,7 +270,7 @@ namespace Ryujinx.Ava
string directory = AppDataManager.Mode switch
{
- AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
+ AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
_ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx")
};
diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json
index a7b490b91..03cddcc84 100644
--- a/src/Ryujinx.Ava/Assets/Locales/en_US.json
+++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json
@@ -637,5 +637,7 @@
"SettingsTabNetworkInterface": "Network Interface:",
"NetworkInterfaceTooltip": "The network interface used for LAN features",
"NetworkInterfaceDefault": "Default",
- "PackagingShaders": "Packaging Shaders"
+ "PackagingShaders": "Packaging Shaders",
+ "AboutChangelogButton": "View Changelog on GitHub",
+ "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser."
}
\ No newline at end of file
diff --git a/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs b/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs
index 7e2afb8bd..f207c5fb0 100644
--- a/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs
+++ b/src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs
@@ -3,21 +3,20 @@ using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Common;
using System;
using System.Collections.Concurrent;
using System.Threading;
-using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Helpers
{
public static class NotificationHelper
{
- private const int MaxNotifications = 4;
+ private const int MaxNotifications = 4;
private const int NotificationDelayInMs = 5000;
private static WindowNotificationManager _notificationManager;
- private static readonly ManualResetEvent _templateAppliedEvent = new(false);
private static readonly BlockingCollection _notifications = new();
public static void SetNotificationManager(Window host)
@@ -29,25 +28,31 @@ namespace Ryujinx.Ava.UI.Helpers
Margin = new Thickness(0, 0, 15, 40)
};
+ var maybeAsyncWorkQueue = new Lazy>(
+ () => new AsyncWorkQueue(notification =>
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ _notificationManager.Show(notification);
+ });
+ },
+ "UI.NotificationThread",
+ _notifications),
+ LazyThreadSafetyMode.ExecutionAndPublication);
+
_notificationManager.TemplateApplied += (sender, args) =>
{
- _templateAppliedEvent.Set();
+ // NOTE: Force creation of the AsyncWorkQueue.
+ _ = maybeAsyncWorkQueue.Value;
};
- Task.Run(async () =>
+ host.Closing += (sender, args) =>
{
- _templateAppliedEvent.WaitOne();
-
- foreach (var notification in _notifications.GetConsumingEnumerable())
+ if (maybeAsyncWorkQueue.IsValueCreated)
{
- Dispatcher.UIThread.Post(() =>
- {
- _notificationManager.Show(notification);
- });
-
- await Task.Delay(NotificationDelayInMs / MaxNotifications);
+ maybeAsyncWorkQueue.Value.Dispose();
}
- });
+ };
}
public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null)
diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
index f8dd41435..e7013968d 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
@@ -1529,6 +1529,8 @@ namespace Ryujinx.Ava.UI.ViewModels
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
+
+ appMetadata.LastPlayed = DateTime.UtcNow;
});
}
diff --git a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml
index 7bd3e20d3..28a851d67 100644
--- a/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml
+++ b/src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml
@@ -72,6 +72,18 @@
LineHeight="12"
Text="{Binding Version}"
TextAlignment="Center" />
+
_context.Capabilities.SupportsViewportMask;
+ public bool QueryHostSupportsDepthClipControl() => _context.Capabilities.SupportsDepthClipControl;
+
///
/// Converts a packed Maxwell texture format to the shader translator texture format.
///
diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
index 7d5fe8931..161191b85 100644
--- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
+++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
@@ -163,6 +163,7 @@ namespace Ryujinx.Graphics.OpenGL
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
+ supportsDepthClipControl: true,
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
maximumStorageBuffersPerStage: 16,
maximumTexturesPerStage: 32,
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
index 8d805e32e..1bd0182b5 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs
@@ -239,33 +239,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine();
}
- bool isFragment = context.Config.Stage == ShaderStage.Fragment;
-
- if (isFragment || context.Config.Stage == ShaderStage.Compute || context.Config.Stage == ShaderStage.Vertex)
+ if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryEarlyZForce())
{
- if (isFragment && context.Config.GpuAccessor.QueryEarlyZForce())
- {
- context.AppendLine("layout(early_fragment_tests) in;");
- context.AppendLine();
- }
-
- if ((context.Config.UsedFeatures & (FeatureFlags.FragCoordXY | FeatureFlags.IntegerSampling)) != 0)
- {
- string stage = OperandManager.GetShaderStagePrefix(context.Config.Stage);
-
- int scaleElements = context.Config.GetTextureDescriptors().Length + context.Config.GetImageDescriptors().Length;
-
- if (isFragment)
- {
- scaleElements++; // Also includes render target scale, for gl_FragCoord.
- }
-
- if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IntegerSampling) && scaleElements != 0)
- {
- AppendHelperFunction(context, $"Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_{stage}.glsl");
- context.AppendLine();
- }
- }
+ context.AppendLine("layout(early_fragment_tests) in;");
+ context.AppendLine();
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.AtomicMinMaxS32Shared) != 0)
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_cp.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_cp.glsl
deleted file mode 100644
index 08c625488..000000000
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_cp.glsl
+++ /dev/null
@@ -1,19 +0,0 @@
-ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex)
-{
- float scale = support_buffer.s_render_scale[1 + samplerIndex];
- if (scale == 1.0)
- {
- return inputVec;
- }
- return ivec2(vec2(inputVec) * scale);
-}
-
-int Helper_TextureSizeUnscale(int size, int samplerIndex)
-{
- float scale = support_buffer.s_render_scale[1 + samplerIndex];
- if (scale == 1.0)
- {
- return size;
- }
- return int(float(size) / scale);
-}
\ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_fp.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_fp.glsl
deleted file mode 100644
index 07a38a7a6..000000000
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_fp.glsl
+++ /dev/null
@@ -1,26 +0,0 @@
-ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex)
-{
- float scale = support_buffer.s_render_scale[1 + samplerIndex];
- if (scale == 1.0)
- {
- return inputVec;
- }
- if (scale < 0.0) // If less than 0, try interpolate between texels by using the screen position.
- {
- return ivec2(vec2(inputVec) * (-scale) + mod(gl_FragCoord.xy, 0.0 - scale));
- }
- else
- {
- return ivec2(vec2(inputVec) * scale);
- }
-}
-
-int Helper_TextureSizeUnscale(int size, int samplerIndex)
-{
- float scale = abs(support_buffer.s_render_scale[1 + samplerIndex]);
- if (scale == 1.0)
- {
- return size;
- }
- return int(float(size) / scale);
-}
\ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_vp.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_vp.glsl
deleted file mode 100644
index 72baa441d..000000000
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_vp.glsl
+++ /dev/null
@@ -1,20 +0,0 @@
-ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex)
-{
- float scale = abs(support_buffer.s_render_scale[1 + samplerIndex + support_buffer.s_frag_scale_count]);
- if (scale == 1.0)
- {
- return inputVec;
- }
-
- return ivec2(vec2(inputVec) * scale);
-}
-
-int Helper_TextureSizeUnscale(int size, int samplerIndex)
-{
- float scale = abs(support_buffer.s_render_scale[1 + samplerIndex + support_buffer.s_frag_scale_count]);
- if (scale == 1.0)
- {
- return size;
- }
- return int(float(size) / scale);
-}
\ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
index 71e40fe7c..6cf36a2a6 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs
@@ -101,6 +101,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.MemoryBarrier, InstType.CallNullary, "memoryBarrier");
Add(Instruction.Minimum, InstType.CallBinary, "min");
Add(Instruction.MinimumU32, InstType.CallBinary, "min");
+ Add(Instruction.Modulo, InstType.CallBinary, "mod");
Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1);
Add(Instruction.MultiplyHighS32, InstType.CallBinary, HelperFunctionNames.MultiplyHighS32);
Add(Instruction.MultiplyHighU32, InstType.CallBinary, HelperFunctionNames.MultiplyHighU32);
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
index ef5260d10..dfc8197b6 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
@@ -97,30 +97,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCallBuilder.Append(str);
}
- string ApplyScaling(string vector)
- {
- if (context.Config.Stage.SupportsRenderScale() &&
- texOp.Inst == Instruction.ImageLoad &&
- !isBindless &&
- !isIndexed)
- {
- // Image scales start after texture ones.
- int scaleIndex = context.Config.GetTextureDescriptors().Length + context.Config.FindImageDescriptorIndex(texOp);
-
- if (pCount == 3 && isArray)
- {
- // The array index is not scaled, just x and y.
- vector = $"ivec3(Helper_TexelFetchScale(({vector}).xy, {scaleIndex}), ({vector}).z)";
- }
- else if (pCount == 2 && !isArray)
- {
- vector = $"Helper_TexelFetchScale({vector}, {scaleIndex})";
- }
- }
-
- return vector;
- }
-
if (pCount > 1)
{
string[] elems = new string[pCount];
@@ -130,7 +106,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
elems[index] = Src(AggregateType.S32);
}
- Append(ApplyScaling($"ivec{pCount}({string.Join(", ", elems)})"));
+ Append($"ivec{pCount}({string.Join(", ", elems)})");
}
else
{
@@ -584,53 +560,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
}
- string ApplyScaling(string vector)
- {
- if (intCoords)
- {
- if (context.Config.Stage.SupportsRenderScale() &&
- !isBindless &&
- !isIndexed)
- {
- int index = context.Config.FindTextureDescriptorIndex(texOp);
-
- if (pCount == 3 && isArray)
- {
- // The array index is not scaled, just x and y.
- vector = "ivec3(Helper_TexelFetchScale((" + vector + ").xy, " + index + "), (" + vector + ").z)";
- }
- else if (pCount == 2 && !isArray)
- {
- vector = "Helper_TexelFetchScale(" + vector + ", " + index + ")";
- }
- }
- }
-
- return vector;
- }
-
- string ApplyBias(string vector)
- {
- int gatherBiasPrecision = context.Config.GpuAccessor.QueryHostGatherBiasPrecision();
- if (isGather && gatherBiasPrecision != 0)
- {
- // GPU requires texture gather to be slightly offset to match NVIDIA behaviour when point is exactly between two texels.
- // Offset by the gather precision divided by 2 to correct for rounding.
-
- if (pCount == 1)
- {
- vector = $"{vector} + (1.0 / (float(textureSize({samplerName}, 0)) * float({1 << (gatherBiasPrecision + 1)})))";
- }
- else
- {
- vector = $"{vector} + (1.0 / (vec{pCount}(textureSize({samplerName}, 0).{"xyz".Substring(0, pCount)}) * float({1 << (gatherBiasPrecision + 1)})))";
- }
- }
-
- return vector;
- }
-
- Append(ApplyBias(ApplyScaling(AssemblePVector(pCount))));
+ Append(AssemblePVector(pCount));
string AssembleDerivativesVector(int count)
{
@@ -750,7 +680,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
else
{
- (TextureDescriptor descriptor, int descriptorIndex) = context.Config.FindTextureDescriptor(texOp);
+ TextureDescriptor descriptor = context.Config.FindTextureDescriptor(texOp);
bool hasLod = !descriptor.Type.HasFlag(SamplerType.Multisample) && descriptor.Type != SamplerType.TextureBuffer;
string texCall;
@@ -767,14 +697,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCall = $"textureSize({samplerName}){GetMask(texOp.Index)}";
}
- if (context.Config.Stage.SupportsRenderScale() &&
- (texOp.Index < 2 || (texOp.Type & SamplerType.Mask) == SamplerType.Texture3D) &&
- !isBindless &&
- !isIndexed)
- {
- texCall = $"Helper_TextureSizeUnscale({texCall}, {descriptorIndex})";
- }
-
return texCall;
}
}
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
index 0ef89b398..7af6d316e 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
@@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public Dictionary OutputsPerPatch { get; } = new Dictionary();
public Instruction CoordTemp { get; set; }
+ public StructuredFunction CurrentFunction { get; set; }
private readonly Dictionary _locals = new Dictionary();
private readonly Dictionary _localForArgs = new Dictionary();
private readonly Dictionary _funcArgs = new Dictionary();
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
index fda0dc47c..f088a47f3 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
@@ -4,7 +4,6 @@ using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
using System.Numerics;
using static Spv.Specification;
@@ -114,6 +113,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.MemoryBarrier, GenerateMemoryBarrier);
Add(Instruction.Minimum, GenerateMinimum);
Add(Instruction.MinimumU32, GenerateMinimumU32);
+ Add(Instruction.Modulo, GenerateModulo);
Add(Instruction.Multiply, GenerateMultiply);
Add(Instruction.MultiplyHighS32, GenerateMultiplyHighS32);
Add(Instruction.MultiplyHighU32, GenerateMultiplyHighU32);
@@ -744,8 +744,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
pCoords = Src(AggregateType.S32);
}
- pCoords = ScalingHelpers.ApplyScaling(context, texOp, pCoords, intCoords: true, isBindless, isIndexed, isArray, pCount);
-
(var imageType, var imageVariable) = context.Images[new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format)];
var image = context.Load(imageType, imageVariable);
@@ -1040,6 +1038,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return GenerateBinaryU32(context, operation, context.Delegates.GlslUMin);
}
+ private static OperationResult GenerateModulo(CodeGenContext context, AstOperation operation)
+ {
+ return GenerateBinary(context, operation, context.Delegates.FMod, null);
+ }
+
private static OperationResult GenerateMultiply(CodeGenContext context, AstOperation operation)
{
return GenerateBinary(context, operation, context.Delegates.FMul, context.Delegates.IMul);
@@ -1101,7 +1104,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateReturn(CodeGenContext context, AstOperation operation)
{
- context.Return();
+ if (operation.SourcesCount != 0)
+ {
+ context.ReturnValue(context.Get(context.CurrentFunction.ReturnType, operation.GetSource(0)));
+ }
+ else
+ {
+ context.Return();
+ }
+
return OperationResult.Invalid;
}
@@ -1439,35 +1450,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
- SpvInstruction ApplyBias(SpvInstruction vector, SpvInstruction image)
- {
- int gatherBiasPrecision = context.Config.GpuAccessor.QueryHostGatherBiasPrecision();
- if (isGather && gatherBiasPrecision != 0)
- {
- // GPU requires texture gather to be slightly offset to match NVIDIA behaviour when point is exactly between two texels.
- // Offset by the gather precision divided by 2 to correct for rounding.
- var sizeType = pCount == 1 ? context.TypeS32() : context.TypeVector(context.TypeS32(), pCount);
- var pVectorType = pCount == 1 ? context.TypeFP32() : context.TypeVector(context.TypeFP32(), pCount);
-
- var bias = context.Constant(context.TypeFP32(), (float)(1 << (gatherBiasPrecision + 1)));
- var biasVector = context.CompositeConstruct(pVectorType, Enumerable.Repeat(bias, pCount).ToArray());
-
- var one = context.Constant(context.TypeFP32(), 1f);
- var oneVector = context.CompositeConstruct(pVectorType, Enumerable.Repeat(one, pCount).ToArray());
-
- var divisor = context.FMul(
- pVectorType,
- context.ConvertSToF(pVectorType, context.ImageQuerySize(sizeType, image)),
- biasVector);
-
- vector = context.FAdd(pVectorType, vector, context.FDiv(pVectorType, oneVector, divisor));
- }
-
- return vector;
- }
-
SpvInstruction pCoords = AssemblePVector(pCount);
- pCoords = ScalingHelpers.ApplyScaling(context, texOp, pCoords, intCoords, isBindless, isIndexed, isArray, pCount);
SpvInstruction AssembleDerivativesVector(int count)
{
@@ -1638,8 +1621,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
image = context.Image(imageType, image);
}
- pCoords = ApplyBias(pCoords, image);
-
var operands = operandsList.ToArray();
SpvInstruction result;
@@ -1755,11 +1736,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
result = context.CompositeExtract(context.TypeS32(), result, (SpvLiteralInteger)texOp.Index);
}
- if (texOp.Index < 2 || (type & SamplerType.Mask) == SamplerType.Texture3D)
- {
- result = ScalingHelpers.ApplyUnscaling(context, texOp.WithType(type), result, isBindless, isIndexed);
- }
-
return new OperationResult(AggregateType.S32, result);
}
}
@@ -2269,7 +2245,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
- if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
+ if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@@ -2280,7 +2256,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
- if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
+ if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@@ -2340,7 +2316,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
- if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
+ if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@@ -2351,7 +2327,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
- if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
+ if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ScalingHelpers.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ScalingHelpers.cs
deleted file mode 100644
index c8b21e881..000000000
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ScalingHelpers.cs
+++ /dev/null
@@ -1,227 +0,0 @@
-using Ryujinx.Graphics.Shader.IntermediateRepresentation;
-using Ryujinx.Graphics.Shader.StructuredIr;
-using Ryujinx.Graphics.Shader.Translation;
-using static Spv.Specification;
-
-namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
-{
- using SpvInstruction = Spv.Generator.Instruction;
-
- static class ScalingHelpers
- {
- public static SpvInstruction ApplyScaling(
- CodeGenContext context,
- AstTextureOperation texOp,
- SpvInstruction vector,
- bool intCoords,
- bool isBindless,
- bool isIndexed,
- bool isArray,
- int pCount)
- {
- if (intCoords)
- {
- if (context.Config.Stage.SupportsRenderScale() &&
- !isBindless &&
- !isIndexed)
- {
- int index = texOp.Inst == Instruction.ImageLoad
- ? context.Config.GetTextureDescriptors().Length + context.Config.FindImageDescriptorIndex(texOp)
- : context.Config.FindTextureDescriptorIndex(texOp);
-
- if (pCount == 3 && isArray)
- {
- return ApplyScaling2DArray(context, vector, index);
- }
- else if (pCount == 2 && !isArray)
- {
- return ApplyScaling2D(context, vector, index);
- }
- }
- }
-
- return vector;
- }
-
- private static SpvInstruction ApplyScaling2DArray(CodeGenContext context, SpvInstruction vector, int index)
- {
- // The array index is not scaled, just x and y.
- var vectorXY = context.VectorShuffle(context.TypeVector(context.TypeS32(), 2), vector, vector, 0, 1);
- var vectorZ = context.CompositeExtract(context.TypeS32(), vector, 2);
- var vectorXYScaled = ApplyScaling2D(context, vectorXY, index);
- var vectorScaled = context.CompositeConstruct(context.TypeVector(context.TypeS32(), 3), vectorXYScaled, vectorZ);
-
- return vectorScaled;
- }
-
- private static SpvInstruction ApplyScaling2D(CodeGenContext context, SpvInstruction vector, int index)
- {
- var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
- var fieldIndex = context.Constant(context.TypeU32(), 4);
- var scaleIndex = context.Constant(context.TypeU32(), index);
-
- if (context.Config.Stage == ShaderStage.Vertex)
- {
- var scaleCountPointerType = context.TypePointer(StorageClass.Uniform, context.TypeS32());
- var scaleCountElemPointer = context.AccessChain(scaleCountPointerType, context.ConstantBuffers[0], context.Constant(context.TypeU32(), 3));
- var scaleCount = context.Load(context.TypeS32(), scaleCountElemPointer);
-
- scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, scaleCount);
- }
-
- scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, context.Constant(context.TypeU32(), 1));
-
- var scaleElemPointer = context.AccessChain(pointerType, context.ConstantBuffers[0], fieldIndex, scaleIndex);
- var scale = context.Load(context.TypeFP32(), scaleElemPointer);
-
- var ivector2Type = context.TypeVector(context.TypeS32(), 2);
- var localVector = context.CoordTemp;
-
- var passthrough = context.FOrdEqual(context.TypeBool(), scale, context.Constant(context.TypeFP32(), 1f));
-
- var mergeLabel = context.Label();
-
- if (context.Config.Stage == ShaderStage.Fragment)
- {
- var scaledInterpolatedLabel = context.Label();
- var scaledNoInterpolationLabel = context.Label();
-
- var needsInterpolation = context.FOrdLessThan(context.TypeBool(), scale, context.Constant(context.TypeFP32(), 0f));
-
- context.SelectionMerge(mergeLabel, SelectionControlMask.MaskNone);
- context.BranchConditional(needsInterpolation, scaledInterpolatedLabel, scaledNoInterpolationLabel);
-
- // scale < 0.0
- context.AddLabel(scaledInterpolatedLabel);
-
- ApplyScalingInterpolated(context, localVector, vector, scale);
- context.Branch(mergeLabel);
-
- // scale >= 0.0
- context.AddLabel(scaledNoInterpolationLabel);
-
- ApplyScalingNoInterpolation(context, localVector, vector, scale);
- context.Branch(mergeLabel);
-
- context.AddLabel(mergeLabel);
-
- var passthroughLabel = context.Label();
- var finalMergeLabel = context.Label();
-
- context.SelectionMerge(finalMergeLabel, SelectionControlMask.MaskNone);
- context.BranchConditional(passthrough, passthroughLabel, finalMergeLabel);
-
- context.AddLabel(passthroughLabel);
-
- context.Store(localVector, vector);
- context.Branch(finalMergeLabel);
-
- context.AddLabel(finalMergeLabel);
-
- return context.Load(ivector2Type, localVector);
- }
- else
- {
- var passthroughLabel = context.Label();
- var scaledLabel = context.Label();
-
- context.SelectionMerge(mergeLabel, SelectionControlMask.MaskNone);
- context.BranchConditional(passthrough, passthroughLabel, scaledLabel);
-
- // scale == 1.0
- context.AddLabel(passthroughLabel);
-
- context.Store(localVector, vector);
- context.Branch(mergeLabel);
-
- // scale != 1.0
- context.AddLabel(scaledLabel);
-
- ApplyScalingNoInterpolation(context, localVector, vector, scale);
- context.Branch(mergeLabel);
-
- context.AddLabel(mergeLabel);
-
- return context.Load(ivector2Type, localVector);
- }
- }
-
- private static void ApplyScalingInterpolated(CodeGenContext context, SpvInstruction output, SpvInstruction vector, SpvInstruction scale)
- {
- var vector2Type = context.TypeVector(context.TypeFP32(), 2);
-
- var scaleNegated = context.FNegate(context.TypeFP32(), scale);
- var scaleVector = context.CompositeConstruct(vector2Type, scaleNegated, scaleNegated);
-
- var vectorFloat = context.ConvertSToF(vector2Type, vector);
- var vectorScaled = context.VectorTimesScalar(vector2Type, vectorFloat, scaleNegated);
-
- var fragCoordPointer = context.Inputs[new IoDefinition(StorageKind.Input, IoVariable.FragmentCoord)];
- var fragCoord = context.Load(context.TypeVector(context.TypeFP32(), 4), fragCoordPointer);
- var fragCoordXY = context.VectorShuffle(vector2Type, fragCoord, fragCoord, 0, 1);
-
- var scaleMod = context.FMod(vector2Type, fragCoordXY, scaleVector);
- var vectorInterpolated = context.FAdd(vector2Type, vectorScaled, scaleMod);
-
- context.Store(output, context.ConvertFToS(context.TypeVector(context.TypeS32(), 2), vectorInterpolated));
- }
-
- private static void ApplyScalingNoInterpolation(CodeGenContext context, SpvInstruction output, SpvInstruction vector, SpvInstruction scale)
- {
- if (context.Config.Stage == ShaderStage.Vertex)
- {
- scale = context.GlslFAbs(context.TypeFP32(), scale);
- }
-
- var vector2Type = context.TypeVector(context.TypeFP32(), 2);
-
- var vectorFloat = context.ConvertSToF(vector2Type, vector);
- var vectorScaled = context.VectorTimesScalar(vector2Type, vectorFloat, scale);
-
- context.Store(output, context.ConvertFToS(context.TypeVector(context.TypeS32(), 2), vectorScaled));
- }
-
- public static SpvInstruction ApplyUnscaling(
- CodeGenContext context,
- AstTextureOperation texOp,
- SpvInstruction size,
- bool isBindless,
- bool isIndexed)
- {
- if (context.Config.Stage.SupportsRenderScale() &&
- !isBindless &&
- !isIndexed)
- {
- int index = context.Config.FindTextureDescriptorIndex(texOp);
-
- var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
- var fieldIndex = context.Constant(context.TypeU32(), 4);
- var scaleIndex = context.Constant(context.TypeU32(), index);
-
- if (context.Config.Stage == ShaderStage.Vertex)
- {
- var scaleCountPointerType = context.TypePointer(StorageClass.Uniform, context.TypeS32());
- var scaleCountElemPointer = context.AccessChain(scaleCountPointerType, context.ConstantBuffers[0], context.Constant(context.TypeU32(), 3));
- var scaleCount = context.Load(context.TypeS32(), scaleCountElemPointer);
-
- scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, scaleCount);
- }
-
- scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, context.Constant(context.TypeU32(), 1));
-
- var scaleElemPointer = context.AccessChain(pointerType, context.ConstantBuffers[0], fieldIndex, scaleIndex);
- var scale = context.GlslFAbs(context.TypeFP32(), context.Load(context.TypeFP32(), scaleElemPointer));
-
- var passthrough = context.FOrdEqual(context.TypeBool(), scale, context.Constant(context.TypeFP32(), 1f));
-
- var sizeFloat = context.ConvertSToF(context.TypeFP32(), size);
- var sizeUnscaled = context.FDiv(context.TypeFP32(), sizeFloat, scale);
- var sizeUnscaledInt = context.ConvertFToS(context.TypeS32(), sizeUnscaled);
-
- return context.Select(context.TypeS32(), passthrough, size, sizeUnscaledInt);
- }
-
- return size;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs
index 3ccfd7f55..0fa954e15 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs
@@ -67,6 +67,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public readonly FuncBinaryInstruction GlslSMax;
public readonly FuncBinaryInstruction GlslFMin;
public readonly FuncBinaryInstruction GlslSMin;
+ public readonly FuncBinaryInstruction FMod;
public readonly FuncBinaryInstruction FMul;
public readonly FuncBinaryInstruction IMul;
public readonly FuncBinaryInstruction FSub;
@@ -174,6 +175,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
GlslSMax = context.GlslSMax;
GlslFMin = context.GlslFMin;
GlslSMin = context.GlslSMin;
+ FMod = context.FMod;
FMul = context.FMul;
IMul = context.IMul;
FSub = context.FSub;
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
index 3e11a9749..a55e09fd3 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
@@ -144,10 +144,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static void Generate(CodeGenContext context, StructuredProgramInfo info, int funcIndex)
{
- var function = info.Functions[funcIndex];
-
- (_, var spvFunc) = context.GetFunction(funcIndex);
+ (var function, var spvFunc) = context.GetFunction(funcIndex);
+ context.CurrentFunction = function;
context.AddFunction(spvFunc);
context.StartFunction();
diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
index 2207156cd..3be5088e4 100644
--- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
+++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
@@ -367,6 +367,15 @@ namespace Ryujinx.Graphics.Shader
return true;
}
+ ///
+ /// Queries whether the host supports depth clip control.
+ ///
+ /// True if the GPU and driver supports depth clip control, false otherwise
+ bool QueryHostSupportsDepthClipControl()
+ {
+ return true;
+ }
+
///
/// Queries the point size from the GPU state, used when it is not explicitly set on the shader.
///
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs
index 3a9e658aa..963a5c656 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs
@@ -187,27 +187,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.Config.GpuAccessor.Log("Shader instruction Longjmp is not implemented.");
}
- public static void P2rR(EmitterContext context)
- {
- InstP2rR op = context.GetOp();
-
- context.Config.GpuAccessor.Log("Shader instruction P2rR is not implemented.");
- }
-
- public static void P2rI(EmitterContext context)
- {
- InstP2rI op = context.GetOp();
-
- context.Config.GpuAccessor.Log("Shader instruction P2rI is not implemented.");
- }
-
- public static void P2rC(EmitterContext context)
- {
- InstP2rC op = context.GetOp();
-
- context.Config.GpuAccessor.Log("Shader instruction P2rC is not implemented.");
- }
-
public static void Pexit(EmitterContext context)
{
InstPexit op = context.GetOp();
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs
index 9992ac378..e12177f7d 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs
@@ -209,21 +209,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
return context.ICompareNotEqual(context.BitwiseAnd(value, Const(1 << bit)), Const(0));
}
- if (ccpr)
+ int count = ccpr ? RegisterConsts.FlagsCount : RegisterConsts.PredsCount;
+ RegisterType type = ccpr ? RegisterType.Flag : RegisterType.Predicate;
+ int shift = (int)byteSel * 8;
+
+ for (int bit = 0; bit < count; bit++)
{
- // TODO: Support Register to condition code flags copy.
- context.Config.GpuAccessor.Log("R2P.CC not implemented.");
- }
- else
- {
- int shift = (int)byteSel * 8;
-
- for (int bit = 0; bit < RegisterConsts.PredsCount; bit++)
- {
- Operand pred = Register(bit, RegisterType.Predicate);
- Operand res = context.ConditionalSelect(Test(mask, bit), Test(value, bit + shift), pred);
- context.Copy(pred, res);
- }
+ Operand flag = Register(bit, type);
+ Operand res = context.ConditionalSelect(Test(mask, bit), Test(value, bit + shift), flag);
+ context.Copy(flag, res);
}
}
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
index d605661ff..79919624e 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
@@ -1,7 +1,6 @@
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
-
using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper;
using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
@@ -50,5 +49,68 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.Copy(Register(op.DestPred, RegisterType.Predicate), p0Res);
context.Copy(Register(op.DestPredInv, RegisterType.Predicate), p1Res);
}
+
+ public static void P2rC(EmitterContext context)
+ {
+ InstP2rC op = context.GetOp();
+
+ Operand srcA = GetSrcReg(context, op.SrcA);
+ Operand dest = GetSrcReg(context, op.Dest);
+ Operand mask = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset);
+
+ EmitP2r(context, srcA, dest, mask, op.ByteSel, op.Ccpr);
+ }
+
+ public static void P2rI(EmitterContext context)
+ {
+ InstP2rI op = context.GetOp();
+
+ Operand srcA = GetSrcReg(context, op.SrcA);
+ Operand dest = GetSrcReg(context, op.Dest);
+ Operand mask = GetSrcImm(context, op.Imm20);
+
+ EmitP2r(context, srcA, dest, mask, op.ByteSel, op.Ccpr);
+ }
+
+ public static void P2rR(EmitterContext context)
+ {
+ InstP2rR op = context.GetOp();
+
+ Operand srcA = GetSrcReg(context, op.SrcA);
+ Operand dest = GetSrcReg(context, op.Dest);
+ Operand mask = GetSrcReg(context, op.SrcB);
+
+ EmitP2r(context, srcA, dest, mask, op.ByteSel, op.Ccpr);
+ }
+
+ private static void EmitP2r(
+ EmitterContext context,
+ Operand srcA,
+ Operand dest,
+ Operand mask,
+ ByteSel byteSel,
+ bool ccpr)
+ {
+ int count = ccpr ? RegisterConsts.FlagsCount : RegisterConsts.PredsCount;
+ int shift = (int)byteSel * 8;
+ mask = context.BitwiseAnd(mask, Const(0xff));
+
+ Operand insert = Const(0);
+ for (int i = 0; i < count; i++)
+ {
+ Operand condition = ccpr
+ ? Register(i, RegisterType.Flag)
+ : Register(i, RegisterType.Predicate);
+
+ Operand bit = context.ConditionalSelect(condition, Const(1 << (i + shift)), Const(0));
+ insert = context.BitwiseOr(insert, bit);
+ }
+
+ Operand maskShifted = context.ShiftLeft(mask, Const(shift));
+ Operand masked = context.BitwiseAnd(srcA, context.BitwiseNot(maskShifted));
+ Operand res = context.BitwiseOr(masked, context.BitwiseAnd(insert, maskShifted));
+
+ context.Copy(dest, res);
+ }
}
}
\ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
index 817755bbe..f7afe5071 100644
--- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
@@ -97,6 +97,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
MemoryBarrier,
Minimum,
MinimumU32,
+ Modulo,
Multiply,
MultiplyHighS32,
MultiplyHighU32,
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs
index 99179f151..d502a9b65 100644
--- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs
@@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public Instruction Inst { get; private set; }
public StorageKind StorageKind { get; }
+ public bool ForcePrecise { get; set; }
+
private Operand[] _dests;
public Operand Dest
diff --git a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
index 3434e2a81..2efcbca4f 100644
--- a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
+++ b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
@@ -4,10 +4,6 @@
net7.0
-
-
-
-
@@ -25,9 +21,6 @@
-
-
-
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs
index 2393fd8d8..4cf729d09 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs
@@ -10,6 +10,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
public Instruction Inst { get; }
public StorageKind StorageKind { get; }
+ public bool ForcePrecise { get; }
public int Index { get; }
@@ -17,10 +18,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public int SourcesCount => _sources.Length;
- public AstOperation(Instruction inst, StorageKind storageKind, IAstNode[] sources, int sourcesCount)
+ public AstOperation(Instruction inst, StorageKind storageKind, bool forcePrecise, IAstNode[] sources, int sourcesCount)
{
Inst = inst;
StorageKind = storageKind;
+ ForcePrecise = forcePrecise;
_sources = sources;
for (int index = 0; index < sources.Length; index++)
@@ -38,12 +40,18 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Index = 0;
}
- public AstOperation(Instruction inst, StorageKind storageKind, int index, IAstNode[] sources, int sourcesCount) : this(inst, storageKind, sources, sourcesCount)
+ public AstOperation(
+ Instruction inst,
+ StorageKind storageKind,
+ bool forcePrecise,
+ int index,
+ IAstNode[] sources,
+ int sourcesCount) : this(inst, storageKind, forcePrecise, sources, sourcesCount)
{
Index = index;
}
- public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, StorageKind.None, sources, sources.Length)
+ public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, StorageKind.None, false, sources, sources.Length)
{
}
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
index a44f13cc0..a4e097eb6 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
@@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
int cbufSlot,
int handle,
int index,
- params IAstNode[] sources) : base(inst, StorageKind.None, index, sources, sources.Length)
+ params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length)
{
Type = type;
Format = format;
@@ -27,10 +27,5 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
CbufSlot = cbufSlot;
Handle = handle;
}
-
- public AstTextureOperation WithType(SamplerType type)
- {
- return new AstTextureOperation(Inst, type, Format, Flags, CbufSlot, Handle, Index);
- }
}
}
\ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs
index ab8132540..44f0fad95 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs
@@ -104,6 +104,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Add(Instruction.MaximumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
Add(Instruction.Minimum, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
Add(Instruction.MinimumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
+ Add(Instruction.Modulo, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
Add(Instruction.Multiply, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
Add(Instruction.MultiplyHighS32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
Add(Instruction.MultiplyHighU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
index 939a52f3e..4405c07aa 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
@@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
static class StructuredProgram
{
- public static StructuredProgramInfo MakeStructuredProgram(Function[] functions, ShaderConfig config)
+ public static StructuredProgramInfo MakeStructuredProgram(IReadOnlyList functions, ShaderConfig config)
{
StructuredProgramContext context = new StructuredProgramContext(config);
- for (int funcIndex = 0; funcIndex < functions.Length; funcIndex++)
+ for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++)
{
Function function = functions[funcIndex];
@@ -156,7 +156,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else
{
- source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount);
+ source = new AstOperation(
+ inst,
+ operation.StorageKind,
+ operation.ForcePrecise,
+ operation.Index,
+ sources,
+ operation.SourcesCount);
}
AggregateType destElemType = destType;
@@ -179,7 +185,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
dest.VarType = destElemType;
- context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, StorageKind.None, new[] { destVec, index }, 2)));
+ context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, StorageKind.None, false, new[] { destVec, index }, 2)));
}
}
else if (operation.Dest != null)
@@ -227,7 +233,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else if (!isCopy)
{
- source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount);
+ source = new AstOperation(
+ inst,
+ operation.StorageKind,
+ operation.ForcePrecise,
+ operation.Index,
+ sources,
+ operation.SourcesCount);
}
else
{
@@ -248,7 +260,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else
{
- context.AddNode(new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount));
+ context.AddNode(new AstOperation(
+ inst,
+ operation.StorageKind,
+ operation.ForcePrecise,
+ operation.Index,
+ sources,
+ operation.SourcesCount));
}
// Those instructions needs to be emulated by using helper functions,
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
index c5ad3683a..a4d079914 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs
@@ -319,7 +319,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
new AstOperand(OperandType.Constant, elemIndex)
};
- return new AstOperation(Instruction.Load, StorageKind.ConstantBuffer, sources, sources.Length);
+ return new AstOperation(Instruction.Load, StorageKind.ConstantBuffer, false, sources, sources.Length);
}
return GetOperand(operand);
diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
index 0c51b16f4..6ca74a379 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
@@ -49,13 +49,17 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly List _operations;
private readonly Dictionary _labels;
- public EmitterContext(DecodedProgram program, ShaderConfig config, bool isNonMain)
+ public EmitterContext()
+ {
+ _operations = new List();
+ _labels = new Dictionary();
+ }
+
+ public EmitterContext(DecodedProgram program, ShaderConfig config, bool isNonMain) : this()
{
Program = program;
Config = config;
IsNonMain = isNonMain;
- _operations = new List();
- _labels = new Dictionary();
EmitStart();
}
@@ -242,7 +246,7 @@ namespace Ryujinx.Graphics.Shader.Translation
this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
}
- if (Config.Options.TargetApi == TargetApi.Vulkan && Config.GpuAccessor.QueryTransformDepthMinusOneToOne())
+ if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne() && !Config.GpuAccessor.QueryHostSupportsDepthClipControl())
{
Operand z = this.Load(StorageKind.Output, IoVariable.Position, null, Const(2));
Operand w = this.Load(StorageKind.Output, IoVariable.Position, null, Const(3));
@@ -279,7 +283,7 @@ namespace Ryujinx.Graphics.Shader.Translation
oldYLocal = null;
}
- if (Config.Options.TargetApi == TargetApi.Vulkan && Config.GpuAccessor.QueryTransformDepthMinusOneToOne())
+ if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne() && !Config.GpuAccessor.QueryHostSupportsDepthClipControl())
{
oldZLocal = Local();
this.Copy(oldZLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(2)));
diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
index e41a28f12..6d4104cee 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
@@ -307,6 +307,11 @@ namespace Ryujinx.Graphics.Shader.Translation
return context.Add(fpType | Instruction.Minimum, Local(), a, b);
}
+ public static Operand FPModulo(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32)
+ {
+ return context.Add(fpType | Instruction.Modulo, Local(), a, b);
+ }
+
public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32)
{
return context.Add(fpType | Instruction.Multiply, Local(), a, b);
@@ -656,7 +661,6 @@ namespace Ryujinx.Graphics.Shader.Translation
public static void Return(this EmitterContext context, Operand returnValue)
{
- context.PrepareForReturn();
context.Add(Instruction.Return, null, returnValue);
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs
new file mode 100644
index 000000000..206facd46
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs
@@ -0,0 +1,134 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ class HelperFunctionManager
+ {
+ private readonly List _functionList;
+ private readonly Dictionary _functionIds;
+ private readonly ShaderStage _stage;
+
+ public HelperFunctionManager(List functionList, ShaderStage stage)
+ {
+ _functionList = functionList;
+ _functionIds = new Dictionary();
+ _stage = stage;
+ }
+
+ public int GetOrCreateFunctionId(HelperFunctionName functionName)
+ {
+ if (_functionIds.TryGetValue(functionName, out int functionId))
+ {
+ return functionId;
+ }
+
+ Function function = GenerateFunction(functionName);
+ functionId = _functionList.Count;
+ _functionList.Add(function);
+ _functionIds.Add(functionName, functionId);
+
+ return functionId;
+ }
+
+ private Function GenerateFunction(HelperFunctionName functionName)
+ {
+ return functionName switch
+ {
+ HelperFunctionName.TexelFetchScale => GenerateTexelFetchScaleFunction(),
+ HelperFunctionName.TextureSizeUnscale => GenerateTextureSizeUnscaleFunction(),
+ _ => throw new ArgumentException($"Invalid function name {functionName}")
+ };
+ }
+
+ private Function GenerateTexelFetchScaleFunction()
+ {
+ EmitterContext context = new EmitterContext();
+
+ Operand input = Argument(0);
+ Operand samplerIndex = Argument(1);
+ Operand index = GetScaleIndex(context, samplerIndex);
+
+ Operand scale = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.RenderScale), index);
+
+ Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f));
+ Operand lblScaleNotOne = Label();
+
+ context.BranchIfFalse(lblScaleNotOne, scaleIsOne);
+ context.Return(input);
+ context.MarkLabel(lblScaleNotOne);
+
+ int inArgumentsCount;
+
+ if (_stage == ShaderStage.Fragment)
+ {
+ Operand scaleIsLessThanZero = context.FPCompareLess(scale, ConstF(0f));
+ Operand lblScaleGreaterOrEqualZero = Label();
+
+ context.BranchIfFalse(lblScaleGreaterOrEqualZero, scaleIsLessThanZero);
+
+ Operand negScale = context.FPNegate(scale);
+ Operand inputScaled = context.FPMultiply(context.IConvertS32ToFP32(input), negScale);
+ Operand fragCoordX = context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(0));
+ Operand fragCoordY = context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(1));
+ Operand fragCoord = context.ConditionalSelect(Argument(2), fragCoordY, fragCoordX);
+ Operand inputBias = context.FPModulo(fragCoord, negScale);
+ Operand inputWithBias = context.FPAdd(inputScaled, inputBias);
+
+ context.Return(context.FP32ConvertToS32(inputWithBias));
+ context.MarkLabel(lblScaleGreaterOrEqualZero);
+
+ inArgumentsCount = 3;
+ }
+ else
+ {
+ inArgumentsCount = 2;
+ }
+
+ Operand inputScaled2 = context.FPMultiply(context.IConvertS32ToFP32(input), scale);
+
+ context.Return(context.FP32ConvertToS32(inputScaled2));
+
+ return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TexelFetchScale", true, inArgumentsCount, 0);
+ }
+
+ private Function GenerateTextureSizeUnscaleFunction()
+ {
+ EmitterContext context = new EmitterContext();
+
+ Operand input = Argument(0);
+ Operand samplerIndex = Argument(1);
+ Operand index = GetScaleIndex(context, samplerIndex);
+
+ Operand scale = context.FPAbsolute(context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.RenderScale), index));
+
+ Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f));
+ Operand lblScaleNotOne = Label();
+
+ context.BranchIfFalse(lblScaleNotOne, scaleIsOne);
+ context.Return(input);
+ context.MarkLabel(lblScaleNotOne);
+
+ Operand inputUnscaled = context.FPDivide(context.IConvertS32ToFP32(input), scale);
+
+ context.Return(context.FP32ConvertToS32(inputUnscaled));
+
+ return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TextureSizeUnscale", true, 2, 0);
+ }
+
+ private Operand GetScaleIndex(EmitterContext context, Operand index)
+ {
+ switch (_stage)
+ {
+ case ShaderStage.Vertex:
+ Operand fragScaleCount = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.FragmentRenderScaleCount));
+ return context.IAdd(Const(1), context.IAdd(index, fragScaleCount));
+ default:
+ return context.IAdd(Const(1), index);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs
new file mode 100644
index 000000000..5accdf65f
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs
@@ -0,0 +1,11 @@
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ enum HelperFunctionName
+ {
+ TexelFetchScale,
+ TextureSizeUnscale
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Rewriter.cs b/src/Ryujinx.Graphics.Shader/Translation/Rewriter.cs
index 711661c9b..866ae5223 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Rewriter.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Rewriter.cs
@@ -11,9 +11,10 @@ namespace Ryujinx.Graphics.Shader.Translation
{
static class Rewriter
{
- public static void RunPass(BasicBlock[] blocks, ShaderConfig config)
+ public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config)
{
bool isVertexShader = config.Stage == ShaderStage.Vertex;
+ bool isImpreciseFragmentShader = config.Stage == ShaderStage.Fragment && config.GpuAccessor.QueryHostReducedPrecision();
bool hasConstantBufferDrawParameters = config.GpuAccessor.QueryHasConstantBufferDrawParameters();
bool hasVectorIndexingBug = config.GpuAccessor.QueryHostHasVectorIndexingBug();
bool supportsSnormBufferTextureFormat = config.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat();
@@ -45,6 +46,11 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
+ if (isImpreciseFragmentShader)
+ {
+ EnableForcePreciseIfNeeded(operation);
+ }
+
if (hasVectorIndexingBug)
{
InsertVectorComponentSelect(node, config);
@@ -54,9 +60,14 @@ namespace Ryujinx.Graphics.Shader.Translation
if (operation is TextureOperation texOp)
{
+ node = InsertTexelFetchScale(hfm, node, config);
+ node = InsertTextureSizeUnscale(hfm, node, config);
+
if (texOp.Inst == Instruction.TextureSample)
{
- node = RewriteTextureSample(node, config);
+ node = InsertCoordNormalization(node, config);
+ node = InsertCoordGatherBias(node, config);
+ node = InsertConstOffsets(node, config);
if (texOp.Type == SamplerType.TextureBuffer && !supportsSnormBufferTextureFormat)
{
@@ -76,6 +87,25 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
+ private static void EnableForcePreciseIfNeeded(Operation operation)
+ {
+ // There are some cases where a small bias is added to values to prevent division by zero.
+ // When operating with reduced precision, it is possible for this bias to get rounded to 0
+ // and cause a division by zero.
+ // To prevent that, we force those operations to be precise even if the host wants
+ // imprecise operations for performance.
+
+ if (operation.Inst == (Instruction.FP32 | Instruction.Divide) &&
+ operation.GetSource(0).Type == OperandType.Constant &&
+ operation.GetSource(0).AsFloat() == 1f &&
+ operation.GetSource(1).AsgOp is Operation addOp &&
+ addOp.Inst == (Instruction.FP32 | Instruction.Add) &&
+ addOp.GetSource(1).Type == OperandType.Constant)
+ {
+ addOp.ForcePrecise = true;
+ }
+ }
+
private static void InsertVectorComponentSelect(LinkedListNode node, ShaderConfig config)
{
Operation operation = (Operation)node.Value;
@@ -344,10 +374,279 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
- private static LinkedListNode RewriteTextureSample(LinkedListNode node, ShaderConfig config)
+ private static LinkedListNode InsertTexelFetchScale(HelperFunctionManager hfm, LinkedListNode node, ShaderConfig config)
{
TextureOperation texOp = (TextureOperation)node.Value;
+ bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
+ bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
+
+ bool isArray = (texOp.Type & SamplerType.Array) != 0;
+ bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
+
+ int coordsCount = texOp.Type.GetDimensions();
+
+ int coordsIndex = isBindless || isIndexed ? 1 : 0;
+
+ bool isImage = IsImageInstructionWithScale(texOp.Inst);
+
+ if ((texOp.Inst == Instruction.TextureSample || isImage) &&
+ (intCoords || isImage) &&
+ !isBindless &&
+ !isIndexed &&
+ config.Stage.SupportsRenderScale() &&
+ TypeSupportsScale(texOp.Type))
+ {
+ int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
+ int samplerIndex = isImage
+ ? config.GetTextureDescriptors().Length + config.FindImageDescriptorIndex(texOp)
+ : config.FindTextureDescriptorIndex(texOp);
+
+ for (int index = 0; index < coordsCount; index++)
+ {
+ Operand scaledCoord = Local();
+ Operand[] callArgs;
+
+ if (config.Stage == ShaderStage.Fragment)
+ {
+ callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex), Const(index) };
+ }
+ else
+ {
+ callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex) };
+ }
+
+ node.List.AddBefore(node, new Operation(Instruction.Call, 0, scaledCoord, callArgs));
+
+ texOp.SetSource(coordsIndex + index, scaledCoord);
+ }
+ }
+
+ return node;
+ }
+
+ private static LinkedListNode InsertTextureSizeUnscale(HelperFunctionManager hfm, LinkedListNode node, ShaderConfig config)
+ {
+ TextureOperation texOp = (TextureOperation)node.Value;
+
+ bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
+ bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
+
+ bool isArray = (texOp.Type & SamplerType.Array) != 0;
+ bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
+
+ if (texOp.Inst == Instruction.TextureSize &&
+ texOp.Index < 2 &&
+ !isBindless &&
+ !isIndexed &&
+ config.Stage.SupportsRenderScale() &&
+ TypeSupportsScale(texOp.Type))
+ {
+ int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale);
+ int samplerIndex = config.FindTextureDescriptorIndex(texOp, ignoreType: true);
+
+ for (int index = texOp.DestsCount - 1; index >= 0; index--)
+ {
+ Operand dest = texOp.GetDest(index);
+
+ Operand unscaledSize = Local();
+
+ // Replace all uses with the unscaled size value.
+ // This must be done before the call is added, since it also is a use of the original size.
+ foreach (INode useOp in dest.UseOps)
+ {
+ for (int srcIndex = 0; srcIndex < useOp.SourcesCount; srcIndex++)
+ {
+ if (useOp.GetSource(srcIndex) == dest)
+ {
+ useOp.SetSource(srcIndex, unscaledSize);
+ }
+ }
+ }
+
+ Operand[] callArgs = new Operand[] { Const(functionId), dest, Const(samplerIndex) };
+
+ node.List.AddAfter(node, new Operation(Instruction.Call, 0, unscaledSize, callArgs));
+ }
+ }
+
+ return node;
+ }
+
+ private static bool IsImageInstructionWithScale(Instruction inst)
+ {
+ // Currently, we don't support scaling images that are modified,
+ // so we only need to care about the load instruction.
+ return inst == Instruction.ImageLoad;
+ }
+
+ private static bool TypeSupportsScale(SamplerType type)
+ {
+ return (type & SamplerType.Mask) == SamplerType.Texture2D;
+ }
+
+ private static LinkedListNode InsertCoordNormalization(LinkedListNode node, ShaderConfig config)
+ {
+ // Emulate non-normalized coordinates by normalizing the coordinates on the shader.
+ // Without normalization, the coordinates are expected to the in the [0, W or H] range,
+ // and otherwise, it is expected to be in the [0, 1] range.
+ // We normalize by dividing the coords by the texture size.
+
+ TextureOperation texOp = (TextureOperation)node.Value;
+
+ bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
+ bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
+
+ bool isCoordNormalized = isBindless || config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot);
+
+ if (isCoordNormalized || intCoords)
+ {
+ return node;
+ }
+
+ bool isArray = (texOp.Type & SamplerType.Array) != 0;
+ bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
+
+ int coordsCount = texOp.Type.GetDimensions();
+ int coordsIndex = isBindless || isIndexed ? 1 : 0;
+
+ config.SetUsedFeature(FeatureFlags.IntegerSampling);
+
+ int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
+
+ for (int index = 0; index < normCoordsCount; index++)
+ {
+ Operand coordSize = Local();
+
+ Operand[] texSizeSources;
+
+ if (isBindless || isIndexed)
+ {
+ texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) };
+ }
+ else
+ {
+ texSizeSources = new Operand[] { Const(0) };
+ }
+
+ node.List.AddBefore(node, new TextureOperation(
+ Instruction.TextureSize,
+ texOp.Type,
+ texOp.Format,
+ texOp.Flags,
+ texOp.CbufSlot,
+ texOp.Handle,
+ index,
+ new[] { coordSize },
+ texSizeSources));
+
+ config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
+
+ Operand source = texOp.GetSource(coordsIndex + index);
+
+ Operand coordNormalized = Local();
+
+ node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize)));
+
+ texOp.SetSource(coordsIndex + index, coordNormalized);
+ }
+
+ return node;
+ }
+
+ private static LinkedListNode InsertCoordGatherBias(LinkedListNode node, ShaderConfig config)
+ {
+ // The gather behavior when the coordinate sits right in the middle of two texels is not well defined.
+ // To ensure the correct texel is sampled, we add a small bias value to the coordinate.
+ // This value is calculated as the minimum value required to change the texel it will sample from,
+ // and is 0 if the host does not require the bias.
+
+ TextureOperation texOp = (TextureOperation)node.Value;
+
+ bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
+ bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
+
+ int gatherBiasPrecision = config.GpuAccessor.QueryHostGatherBiasPrecision();
+
+ if (!isGather || gatherBiasPrecision == 0)
+ {
+ return node;
+ }
+
+ bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
+
+ bool isArray = (texOp.Type & SamplerType.Array) != 0;
+ bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
+
+ int coordsCount = texOp.Type.GetDimensions();
+ int coordsIndex = isBindless || isIndexed ? 1 : 0;
+
+ config.SetUsedFeature(FeatureFlags.IntegerSampling);
+
+ int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
+
+ for (int index = 0; index < normCoordsCount; index++)
+ {
+ Operand coordSize = Local();
+ Operand scaledSize = Local();
+ Operand bias = Local();
+
+ Operand[] texSizeSources;
+
+ if (isBindless || isIndexed)
+ {
+ texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) };
+ }
+ else
+ {
+ texSizeSources = new Operand[] { Const(0) };
+ }
+
+ node.List.AddBefore(node, new TextureOperation(
+ Instruction.TextureSize,
+ texOp.Type,
+ texOp.Format,
+ texOp.Flags,
+ texOp.CbufSlot,
+ texOp.Handle,
+ index,
+ new[] { coordSize },
+ texSizeSources));
+
+ config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
+
+ node.List.AddBefore(node, new Operation(
+ Instruction.FP32 | Instruction.Multiply,
+ scaledSize,
+ GenerateI2f(node, coordSize),
+ ConstF((float)(1 << (gatherBiasPrecision + 1)))));
+ node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, bias, ConstF(1f), scaledSize));
+
+ Operand source = texOp.GetSource(coordsIndex + index);
+
+ Operand coordBiased = Local();
+
+ node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordBiased, source, bias));
+
+ texOp.SetSource(coordsIndex + index, coordBiased);
+ }
+
+ return node;
+ }
+
+ private static LinkedListNode InsertConstOffsets(LinkedListNode node, ShaderConfig config)
+ {
+ // Non-constant texture offsets are not allowed (according to the spec),
+ // however some GPUs does support that.
+ // For GPUs where it is not supported, we can replace the instruction with the following:
+ // For texture*Offset, we replace it by texture*, and add the offset to the P coords.
+ // The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords).
+ // For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly.
+ // For textureGatherOffset, we split the operation into up to 4 operations, one for each component
+ // that is accessed, where each textureGather operation has a different offset for each pixel.
+
+ TextureOperation texOp = (TextureOperation)node.Value;
+
bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
@@ -355,9 +654,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
- bool isCoordNormalized = isBindless || config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot);
-
- if (!hasInvalidOffset && isCoordNormalized)
+ if (!hasInvalidOffset)
{
return node;
}
@@ -454,7 +751,7 @@ namespace Ryujinx.Graphics.Shader.Translation
hasInvalidOffset &= !areAllOffsetsConstant;
- if (!hasInvalidOffset && isCoordNormalized)
+ if (!hasInvalidOffset)
{
return node;
}
@@ -473,63 +770,6 @@ namespace Ryujinx.Graphics.Shader.Translation
int componentIndex = texOp.Index;
- Operand Float(Operand value)
- {
- Operand res = Local();
-
- node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP32, res, value));
-
- return res;
- }
-
- // Emulate non-normalized coordinates by normalizing the coordinates on the shader.
- // Without normalization, the coordinates are expected to the in the [0, W or H] range,
- // and otherwise, it is expected to be in the [0, 1] range.
- // We normalize by dividing the coords by the texture size.
- if (!isCoordNormalized && !intCoords)
- {
- config.SetUsedFeature(FeatureFlags.IntegerSampling);
-
- int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
-
- for (int index = 0; index < normCoordsCount; index++)
- {
- Operand coordSize = Local();
-
- Operand[] texSizeSources;
-
- if (isBindless || isIndexed)
- {
- texSizeSources = new Operand[] { sources[0], Const(0) };
- }
- else
- {
- texSizeSources = new Operand[] { Const(0) };
- }
-
- node.List.AddBefore(node, new TextureOperation(
- Instruction.TextureSize,
- texOp.Type,
- texOp.Format,
- texOp.Flags,
- texOp.CbufSlot,
- texOp.Handle,
- index,
- new[] { coordSize },
- texSizeSources));
-
- config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
-
- Operand source = sources[coordsIndex + index];
-
- Operand coordNormalized = Local();
-
- node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, Float(coordSize)));
-
- sources[coordsIndex + index] = coordNormalized;
- }
- }
-
Operand[] dests = new Operand[texOp.DestsCount];
for (int i = 0; i < texOp.DestsCount; i++)
@@ -541,15 +781,7 @@ namespace Ryujinx.Graphics.Shader.Translation
LinkedListNode oldNode = node;
- // Technically, non-constant texture offsets are not allowed (according to the spec),
- // however some GPUs does support that.
- // For GPUs where it is not supported, we can replace the instruction with the following:
- // For texture*Offset, we replace it by texture*, and add the offset to the P coords.
- // The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords).
- // For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly.
- // For textureGatherOffset, we split the operation into up to 4 operations, one for each component
- // that is accessed, where each textureGather operation has a different offset for each pixel.
- if (hasInvalidOffset && isGather && !isShadow)
+ if (isGather && !isShadow)
{
config.SetUsedFeature(FeatureFlags.IntegerSampling);
@@ -557,7 +789,7 @@ namespace Ryujinx.Graphics.Shader.Translation
sources.CopyTo(newSources, 0);
- Operand[] texSizes = InsertTextureSize(node, texOp, lodSources, bindlessHandle, coordsCount);
+ Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
int destIndex = 0;
@@ -576,7 +808,11 @@ namespace Ryujinx.Graphics.Shader.Translation
Operand intOffset = offsets[index + (hasOffsets ? compIndex * coordsCount : 0)];
- node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, offset, Float(intOffset), Float(texSizes[index])));
+ node.List.AddBefore(node, new Operation(
+ Instruction.FP32 | Instruction.Divide,
+ offset,
+ GenerateI2f(node, intOffset),
+ GenerateI2f(node, texSizes[index])));
Operand source = sources[coordsIndex + index];
@@ -603,45 +839,46 @@ namespace Ryujinx.Graphics.Shader.Translation
}
else
{
- if (hasInvalidOffset)
+ if (intCoords)
{
- if (intCoords)
+ for (int index = 0; index < coordsCount; index++)
{
- for (int index = 0; index < coordsCount; index++)
- {
- Operand source = sources[coordsIndex + index];
+ Operand source = sources[coordsIndex + index];
- Operand coordPlusOffset = Local();
+ Operand coordPlusOffset = Local();
- node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index]));
+ node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index]));
- sources[coordsIndex + index] = coordPlusOffset;
- }
+ sources[coordsIndex + index] = coordPlusOffset;
}
- else
+ }
+ else
+ {
+ config.SetUsedFeature(FeatureFlags.IntegerSampling);
+
+ Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
+
+ for (int index = 0; index < coordsCount; index++)
{
- config.SetUsedFeature(FeatureFlags.IntegerSampling);
+ config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
- Operand[] texSizes = InsertTextureSize(node, texOp, lodSources, bindlessHandle, coordsCount);
+ Operand offset = Local();
- for (int index = 0; index < coordsCount; index++)
- {
- config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
+ Operand intOffset = offsets[index];
- Operand offset = Local();
+ node.List.AddBefore(node, new Operation(
+ Instruction.FP32 | Instruction.Divide,
+ offset,
+ GenerateI2f(node, intOffset),
+ GenerateI2f(node, texSizes[index])));
- Operand intOffset = offsets[index];
+ Operand source = sources[coordsIndex + index];
- node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, offset, Float(intOffset), Float(texSizes[index])));
+ Operand coordPlusOffset = Local();
- Operand source = sources[coordsIndex + index];
+ node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordPlusOffset, source, offset));
- Operand coordPlusOffset = Local();
-
- node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordPlusOffset, source, offset));
-
- sources[coordsIndex + index] = coordPlusOffset;
- }
+ sources[coordsIndex + index] = coordPlusOffset;
}
}
@@ -669,22 +906,13 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
- private static Operand[] InsertTextureSize(
+ private static Operand[] InsertTextureLod(
LinkedListNode node,
TextureOperation texOp,
Operand[] lodSources,
Operand bindlessHandle,
int coordsCount)
{
- Operand Int(Operand value)
- {
- Operand res = Local();
-
- node.List.AddBefore(node, new Operation(Instruction.ConvertFP32ToS32, res, value));
-
- return res;
- }
-
Operand[] texSizes = new Operand[coordsCount];
Operand lod = Local();
@@ -708,11 +936,11 @@ namespace Ryujinx.Graphics.Shader.Translation
if (bindlessHandle != null)
{
- texSizeSources = new Operand[] { bindlessHandle, Int(lod) };
+ texSizeSources = new Operand[] { bindlessHandle, GenerateF2i(node, lod) };
}
else
{
- texSizeSources = new Operand[] { Int(lod) };
+ texSizeSources = new Operand[] { GenerateF2i(node, lod) };
}
node.List.AddBefore(node, new TextureOperation(
@@ -796,6 +1024,24 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
+ private static Operand GenerateI2f(LinkedListNode node, Operand value)
+ {
+ Operand res = Local();
+
+ node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP32, res, value));
+
+ return res;
+ }
+
+ private static Operand GenerateF2i(LinkedListNode node, Operand value)
+ {
+ Operand res = Local();
+
+ node.List.AddBefore(node, new Operation(Instruction.ConvertFP32ToS32, res, value));
+
+ return res;
+ }
+
private static bool ReplaceConstantBufferWithDrawParameters(LinkedListNode node, Operation operation)
{
Operand GenerateLoad(IoVariable ioVariable)
diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
index 775607972..73525cb27 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
@@ -860,7 +860,7 @@ namespace Ryujinx.Graphics.Shader.Translation
return descriptors;
}
- public (TextureDescriptor, int) FindTextureDescriptor(AstTextureOperation texOp)
+ public TextureDescriptor FindTextureDescriptor(AstTextureOperation texOp)
{
TextureDescriptor[] descriptors = GetTextureDescriptors();
@@ -872,11 +872,11 @@ namespace Ryujinx.Graphics.Shader.Translation
descriptor.HandleIndex == texOp.Handle &&
descriptor.Format == texOp.Format)
{
- return (descriptor, i);
+ return descriptor;
}
}
- return (default, -1);
+ return default;
}
private static int FindDescriptorIndex(TextureDescriptor[] array, AstTextureOperation texOp)
@@ -897,12 +897,30 @@ namespace Ryujinx.Graphics.Shader.Translation
return -1;
}
- public int FindTextureDescriptorIndex(AstTextureOperation texOp)
+ private static int FindDescriptorIndex(TextureDescriptor[] array, TextureOperation texOp, bool ignoreType = false)
{
- return FindDescriptorIndex(GetTextureDescriptors(), texOp);
+ for (int i = 0; i < array.Length; i++)
+ {
+ var descriptor = array[i];
+
+ if ((descriptor.Type == texOp.Type || ignoreType) &&
+ descriptor.CbufSlot == texOp.CbufSlot &&
+ descriptor.HandleIndex == texOp.Handle &&
+ descriptor.Format == texOp.Format)
+ {
+ return i;
+ }
+ }
+
+ return -1;
}
- public int FindImageDescriptorIndex(AstTextureOperation texOp)
+ public int FindTextureDescriptorIndex(TextureOperation texOp, bool ignoreType = false)
+ {
+ return FindDescriptorIndex(GetTextureDescriptors(), texOp, ignoreType);
+ }
+
+ public int FindImageDescriptorIndex(TextureOperation texOp)
{
return FindDescriptorIndex(GetImageDescriptors(), texOp);
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs
index 53f1e8475..867e24379 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs
@@ -1,11 +1,11 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
-using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation
{
static class ShaderIdentifier
{
- public static ShaderIdentification Identify(Function[] functions, ShaderConfig config)
+ public static ShaderIdentification Identify(IReadOnlyList functions, ShaderConfig config)
{
if (config.Stage == ShaderStage.Geometry &&
config.GpuAccessor.QueryPrimitiveTopology() == InputTopology.Triangles &&
@@ -20,12 +20,12 @@ namespace Ryujinx.Graphics.Shader.Translation
return ShaderIdentification.None;
}
- private static bool IsLayerPassthroughGeometryShader(Function[] functions, out int layerInputAttr)
+ private static bool IsLayerPassthroughGeometryShader(IReadOnlyList functions, out int layerInputAttr)
{
bool writesLayer = false;
layerInputAttr = 0;
- if (functions.Length != 1)
+ if (functions.Count != 1)
{
return false;
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Translator.cs b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs
index 87d97e52e..5bbc00097 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Translator.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs
@@ -5,6 +5,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
using System;
+using System.Collections.Generic;
using System.Linq;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
@@ -44,7 +45,14 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
- Function[] funcs = new Function[functions.Length];
+ List funcs = new List(functions.Length);
+
+ for (int i = 0; i < functions.Length; i++)
+ {
+ funcs.Add(null);
+ }
+
+ HelperFunctionManager hfm = new HelperFunctionManager(funcs, config.Stage);
for (int i = 0; i < functions.Length; i++)
{
@@ -71,7 +79,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Ssa.Rename(cfg.Blocks);
Optimizer.RunPass(cfg.Blocks, config);
- Rewriter.RunPass(cfg.Blocks, config);
+ Rewriter.RunPass(hfm, cfg.Blocks, config);
}
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
diff --git a/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
index 7019dfd91..a2ebc518e 100644
--- a/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
+++ b/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
@@ -65,6 +65,13 @@ namespace Ryujinx.Graphics.Vulkan
return (formatFeatureFlags & flags) == flags;
}
+ public bool BufferFormatSupports(FormatFeatureFlags flags, VkFormat format)
+ {
+ _api.GetPhysicalDeviceFormatProperties(_physicalDevice, format, out var fp);
+
+ return (fp.BufferFeatures & flags) == flags;
+ }
+
public bool OptimalFormatSupports(FormatFeatureFlags flags, GAL.Format format)
{
var formatFeatureFlags = _optimalTable[(int)format];
diff --git a/src/Ryujinx.Graphics.Vulkan/FormatTable.cs b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs
index 45fc46ada..a030d8c85 100644
--- a/src/Ryujinx.Graphics.Vulkan/FormatTable.cs
+++ b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs
@@ -168,5 +168,223 @@ namespace Ryujinx.Graphics.Vulkan
{
return _table[(int)format];
}
+
+ public static int GetAttributeFormatSize(VkFormat format)
+ {
+ switch (format)
+ {
+ case VkFormat.R8Unorm:
+ case VkFormat.R8SNorm:
+ case VkFormat.R8Uint:
+ case VkFormat.R8Sint:
+ case VkFormat.R8Uscaled:
+ case VkFormat.R8Sscaled:
+ return 1;
+
+ case VkFormat.R8G8Unorm:
+ case VkFormat.R8G8SNorm:
+ case VkFormat.R8G8Uint:
+ case VkFormat.R8G8Sint:
+ case VkFormat.R8G8Uscaled:
+ case VkFormat.R8G8Sscaled:
+ case VkFormat.R16Sfloat:
+ case VkFormat.R16Unorm:
+ case VkFormat.R16SNorm:
+ case VkFormat.R16Uint:
+ case VkFormat.R16Sint:
+ case VkFormat.R16Uscaled:
+ case VkFormat.R16Sscaled:
+ return 2;
+
+ case VkFormat.R8G8B8Unorm:
+ case VkFormat.R8G8B8SNorm:
+ case VkFormat.R8G8B8Uint:
+ case VkFormat.R8G8B8Sint:
+ case VkFormat.R8G8B8Uscaled:
+ case VkFormat.R8G8B8Sscaled:
+ return 3;
+
+ case VkFormat.R8G8B8A8Unorm:
+ case VkFormat.R8G8B8A8SNorm:
+ case VkFormat.R8G8B8A8Uint:
+ case VkFormat.R8G8B8A8Sint:
+ case VkFormat.R8G8B8A8Srgb:
+ case VkFormat.R8G8B8A8Uscaled:
+ case VkFormat.R8G8B8A8Sscaled:
+ case VkFormat.B8G8R8A8Unorm:
+ case VkFormat.B8G8R8A8Srgb:
+ case VkFormat.R16G16Sfloat:
+ case VkFormat.R16G16Unorm:
+ case VkFormat.R16G16SNorm:
+ case VkFormat.R16G16Uint:
+ case VkFormat.R16G16Sint:
+ case VkFormat.R16G16Uscaled:
+ case VkFormat.R16G16Sscaled:
+ case VkFormat.R32Sfloat:
+ case VkFormat.R32Uint:
+ case VkFormat.R32Sint:
+ case VkFormat.A2B10G10R10UnormPack32:
+ case VkFormat.A2B10G10R10UintPack32:
+ case VkFormat.B10G11R11UfloatPack32:
+ case VkFormat.E5B9G9R9UfloatPack32:
+ case VkFormat.A2B10G10R10SNormPack32:
+ case VkFormat.A2B10G10R10SintPack32:
+ case VkFormat.A2B10G10R10UscaledPack32:
+ case VkFormat.A2B10G10R10SscaledPack32:
+ return 4;
+
+ case VkFormat.R16G16B16Sfloat:
+ case VkFormat.R16G16B16Unorm:
+ case VkFormat.R16G16B16SNorm:
+ case VkFormat.R16G16B16Uint:
+ case VkFormat.R16G16B16Sint:
+ case VkFormat.R16G16B16Uscaled:
+ case VkFormat.R16G16B16Sscaled:
+ return 6;
+
+ case VkFormat.R16G16B16A16Sfloat:
+ case VkFormat.R16G16B16A16Unorm:
+ case VkFormat.R16G16B16A16SNorm:
+ case VkFormat.R16G16B16A16Uint:
+ case VkFormat.R16G16B16A16Sint:
+ case VkFormat.R16G16B16A16Uscaled:
+ case VkFormat.R16G16B16A16Sscaled:
+ case VkFormat.R32G32Sfloat:
+ case VkFormat.R32G32Uint:
+ case VkFormat.R32G32Sint:
+ return 8;
+
+ case VkFormat.R32G32B32Sfloat:
+ case VkFormat.R32G32B32Uint:
+ case VkFormat.R32G32B32Sint:
+ return 12;
+
+ case VkFormat.R32G32B32A32Sfloat:
+ case VkFormat.R32G32B32A32Uint:
+ case VkFormat.R32G32B32A32Sint:
+ return 16;
+ }
+
+ return 1;
+ }
+
+ public static VkFormat DropLastComponent(VkFormat format)
+ {
+ switch (format)
+ {
+ case VkFormat.R8G8Unorm:
+ return VkFormat.R8Unorm;
+ case VkFormat.R8G8SNorm:
+ return VkFormat.R8SNorm;
+ case VkFormat.R8G8Uint:
+ return VkFormat.R8Uint;
+ case VkFormat.R8G8Sint:
+ return VkFormat.R8Sint;
+ case VkFormat.R8G8Uscaled:
+ return VkFormat.R8Uscaled;
+ case VkFormat.R8G8Sscaled:
+ return VkFormat.R8Sscaled;
+
+ case VkFormat.R8G8B8Unorm:
+ return VkFormat.R8G8Unorm;
+ case VkFormat.R8G8B8SNorm:
+ return VkFormat.R8G8SNorm;
+ case VkFormat.R8G8B8Uint:
+ return VkFormat.R8G8Uint;
+ case VkFormat.R8G8B8Sint:
+ return VkFormat.R8G8Sint;
+ case VkFormat.R8G8B8Uscaled:
+ return VkFormat.R8G8Uscaled;
+ case VkFormat.R8G8B8Sscaled:
+ return VkFormat.R8G8Sscaled;
+
+ case VkFormat.R8G8B8A8Unorm:
+ return VkFormat.R8G8B8Unorm;
+ case VkFormat.R8G8B8A8SNorm:
+ return VkFormat.R8G8B8SNorm;
+ case VkFormat.R8G8B8A8Uint:
+ return VkFormat.R8G8B8Uint;
+ case VkFormat.R8G8B8A8Sint:
+ return VkFormat.R8G8B8Sint;
+ case VkFormat.R8G8B8A8Srgb:
+ return VkFormat.R8G8B8Srgb;
+ case VkFormat.R8G8B8A8Uscaled:
+ return VkFormat.R8G8B8Uscaled;
+ case VkFormat.R8G8B8A8Sscaled:
+ return VkFormat.R8G8B8Sscaled;
+ case VkFormat.B8G8R8A8Unorm:
+ return VkFormat.B8G8R8Unorm;
+ case VkFormat.B8G8R8A8Srgb:
+ return VkFormat.B8G8R8Srgb;
+
+ case VkFormat.R16G16Sfloat:
+ return VkFormat.R16Sfloat;
+ case VkFormat.R16G16Unorm:
+ return VkFormat.R16Unorm;
+ case VkFormat.R16G16SNorm:
+ return VkFormat.R16SNorm;
+ case VkFormat.R16G16Uint:
+ return VkFormat.R16Uint;
+ case VkFormat.R16G16Sint:
+ return VkFormat.R16Sint;
+ case VkFormat.R16G16Uscaled:
+ return VkFormat.R16Uscaled;
+ case VkFormat.R16G16Sscaled:
+ return VkFormat.R16Sscaled;
+
+ case VkFormat.R16G16B16Sfloat:
+ return VkFormat.R16G16Sfloat;
+ case VkFormat.R16G16B16Unorm:
+ return VkFormat.R16G16Unorm;
+ case VkFormat.R16G16B16SNorm:
+ return VkFormat.R16G16SNorm;
+ case VkFormat.R16G16B16Uint:
+ return VkFormat.R16G16Uint;
+ case VkFormat.R16G16B16Sint:
+ return VkFormat.R16G16Sint;
+ case VkFormat.R16G16B16Uscaled:
+ return VkFormat.R16G16Uscaled;
+ case VkFormat.R16G16B16Sscaled:
+ return VkFormat.R16G16Sscaled;
+
+ case VkFormat.R16G16B16A16Sfloat:
+ return VkFormat.R16G16B16Sfloat;
+ case VkFormat.R16G16B16A16Unorm:
+ return VkFormat.R16G16B16Unorm;
+ case VkFormat.R16G16B16A16SNorm:
+ return VkFormat.R16G16B16SNorm;
+ case VkFormat.R16G16B16A16Uint:
+ return VkFormat.R16G16B16Uint;
+ case VkFormat.R16G16B16A16Sint:
+ return VkFormat.R16G16B16Sint;
+ case VkFormat.R16G16B16A16Uscaled:
+ return VkFormat.R16G16B16Uscaled;
+ case VkFormat.R16G16B16A16Sscaled:
+ return VkFormat.R16G16B16Sscaled;
+
+ case VkFormat.R32G32Sfloat:
+ return VkFormat.R32Sfloat;
+ case VkFormat.R32G32Uint:
+ return VkFormat.R32Uint;
+ case VkFormat.R32G32Sint:
+ return VkFormat.R32Sint;
+
+ case VkFormat.R32G32B32Sfloat:
+ return VkFormat.R32G32Sfloat;
+ case VkFormat.R32G32B32Uint:
+ return VkFormat.R32G32Uint;
+ case VkFormat.R32G32B32Sint:
+ return VkFormat.R32G32Sint;
+
+ case VkFormat.R32G32B32A32Sfloat:
+ return VkFormat.R32G32B32Sfloat;
+ case VkFormat.R32G32B32A32Uint:
+ return VkFormat.R32G32B32Uint;
+ case VkFormat.R32G32B32A32Sint:
+ return VkFormat.R32G32B32Sint;
+ }
+
+ return format;
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
index cde992028..0437a402c 100644
--- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
+++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
@@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Vulkan
public uint[] AttachmentSamples { get; }
public VkFormat[] AttachmentFormats { get; }
public int[] AttachmentIndices { get; }
+ public uint AttachmentIntegerFormatMask { get; }
public int AttachmentsCount { get; }
public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[AttachmentIndices.Length - 1] : -1;
@@ -74,6 +75,7 @@ namespace Ryujinx.Graphics.Vulkan
int index = 0;
int bindIndex = 0;
+ uint attachmentIntegerFormatMask = 0;
foreach (ITexture color in colors)
{
@@ -89,6 +91,11 @@ namespace Ryujinx.Graphics.Vulkan
AttachmentFormats[index] = texture.VkFormat;
AttachmentIndices[index] = bindIndex;
+ if (texture.Info.Format.IsInteger())
+ {
+ attachmentIntegerFormatMask |= 1u << bindIndex;
+ }
+
width = Math.Min(width, (uint)texture.Width);
height = Math.Min(height, (uint)texture.Height);
layers = Math.Min(layers, (uint)texture.Layers);
@@ -102,6 +109,8 @@ namespace Ryujinx.Graphics.Vulkan
bindIndex++;
}
+ AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
+
if (depthStencil is TextureView dsTexture && dsTexture.Valid)
{
_attachments[count - 1] = dsTexture.GetImageViewForAttachment();
diff --git a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs
index 5cab4113a..f600d93f0 100644
--- a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs
+++ b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs
@@ -28,6 +28,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsSubgroupSizeControl;
public readonly bool SupportsShaderInt8;
public readonly bool SupportsShaderStencilExport;
+ public readonly bool SupportsShaderStorageImageMultisample;
public readonly bool SupportsConditionalRendering;
public readonly bool SupportsExtendedDynamicState;
public readonly bool SupportsMultiView;
@@ -42,6 +43,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsGeometryShader;
public readonly bool SupportsViewportArray2;
public readonly bool SupportsHostImportedMemory;
+ public readonly bool SupportsDepthClipControl;
public readonly uint MinSubgroupSize;
public readonly uint MaxSubgroupSize;
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
@@ -63,6 +65,7 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsSubgroupSizeControl,
bool supportsShaderInt8,
bool supportsShaderStencilExport,
+ bool supportsShaderStorageImageMultisample,
bool supportsConditionalRendering,
bool supportsExtendedDynamicState,
bool supportsMultiView,
@@ -77,6 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsGeometryShader,
bool supportsViewportArray2,
bool supportsHostImportedMemory,
+ bool supportsDepthClipControl,
uint minSubgroupSize,
uint maxSubgroupSize,
ShaderStageFlags requiredSubgroupSizeStages,
@@ -97,6 +101,7 @@ namespace Ryujinx.Graphics.Vulkan
SupportsSubgroupSizeControl = supportsSubgroupSizeControl;
SupportsShaderInt8 = supportsShaderInt8;
SupportsShaderStencilExport = supportsShaderStencilExport;
+ SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;
SupportsConditionalRendering = supportsConditionalRendering;
SupportsExtendedDynamicState = supportsExtendedDynamicState;
SupportsMultiView = supportsMultiView;
@@ -111,6 +116,7 @@ namespace Ryujinx.Graphics.Vulkan
SupportsGeometryShader = supportsGeometryShader;
SupportsViewportArray2 = supportsViewportArray2;
SupportsHostImportedMemory = supportsHostImportedMemory;
+ SupportsDepthClipControl = supportsDepthClipControl;
MinSubgroupSize = minSubgroupSize;
MaxSubgroupSize = maxSubgroupSize;
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index ce6148e2f..dcffa2473 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -80,6 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
private PipelineColorBlendAttachmentState[] _storedBlend;
+ private ulong _drawCountSinceBarrier;
public ulong DrawCount { get; private set; }
public bool RenderPassActive { get; private set; }
@@ -133,6 +134,18 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe void Barrier()
{
+ if (_drawCountSinceBarrier != DrawCount)
+ {
+ _drawCountSinceBarrier = DrawCount;
+
+ // Barriers apparently have no effect inside a render pass on MoltenVK.
+ // As a workaround, end the render pass.
+ if (Gd.IsMoltenVk)
+ {
+ EndRenderPass();
+ }
+ }
+
MemoryBarrier memoryBarrier = new MemoryBarrier()
{
SType = StructureType.MemoryBarrier,
@@ -551,7 +564,6 @@ namespace Ryujinx.Graphics.Vulkan
(uint)maxDrawCount,
(uint)stride);
}
-
}
else
{
@@ -813,8 +825,12 @@ namespace Ryujinx.Graphics.Vulkan
public void SetDepthMode(DepthMode mode)
{
- // Currently this is emulated on the shader, because Vulkan had no support for changing the depth mode.
- // In the future, we may want to use the VK_EXT_depth_clip_control extension to change it here.
+ bool oldMode = _newState.DepthMode;
+ _newState.DepthMode = mode == DepthMode.MinusOneToOne;
+ if (_newState.DepthMode != oldMode)
+ {
+ SignalStateChange();
+ }
}
public void SetDepthTest(DepthTestDescriptor depthTest)
@@ -1471,6 +1487,7 @@ namespace Ryujinx.Graphics.Vulkan
{
var dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan();
FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats);
+ _newState.Internal.AttachmentIntegerFormatMask = FramebufferParams.AttachmentIntegerFormatMask;
for (int i = FramebufferParams.AttachmentFormats.Length; i < dstAttachmentFormats.Length; i++)
{
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
index da480d9f5..79179ce07 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
@@ -294,6 +294,7 @@ namespace Ryujinx.Graphics.Vulkan
int attachmentCount = 0;
int maxColorAttachmentIndex = -1;
+ uint attachmentIntegerFormatMask = 0;
for (int i = 0; i < Constants.MaxRenderTargets; i++)
{
@@ -301,6 +302,11 @@ namespace Ryujinx.Graphics.Vulkan
{
pipeline.Internal.AttachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]);
maxColorAttachmentIndex = i;
+
+ if (state.AttachmentFormats[i].IsInteger())
+ {
+ attachmentIntegerFormatMask |= 1u << i;
+ }
}
}
@@ -311,6 +317,7 @@ namespace Ryujinx.Graphics.Vulkan
pipeline.ColorBlendAttachmentStateCount = (uint)(maxColorAttachmentIndex + 1);
pipeline.VertexAttributeDescriptionsCount = (uint)Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
+ pipeline.Internal.AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
return pipeline;
}
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
index dccc8ce68..7e803913f 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
@@ -1,5 +1,7 @@
-using Silk.NET.Vulkan;
+using Ryujinx.Common.Memory;
+using Silk.NET.Vulkan;
using System;
+using System.Numerics;
namespace Ryujinx.Graphics.Vulkan
{
@@ -303,11 +305,19 @@ namespace Ryujinx.Graphics.Vulkan
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
}
+ public bool DepthMode
+ {
+ get => ((Internal.Id9 >> 6) & 0x1) != 0UL;
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
+ }
+
public NativeArray Stages;
public NativeArray StageRequiredSubgroupSizes;
public PipelineLayout PipelineLayout;
public SpecData SpecializationData;
+ private Array32 _vertexAttributeDescriptions2;
+
public void Initialize()
{
Stages = new NativeArray(Constants.MaxShaderStages);
@@ -328,6 +338,7 @@ namespace Ryujinx.Graphics.Vulkan
LineWidth = 1f;
SamplesCount = 1;
+ DepthMode = true;
}
public unsafe Auto CreateComputePipeline(
@@ -400,7 +411,15 @@ namespace Ryujinx.Graphics.Vulkan
Pipeline pipelineHandle = default;
+ bool isMoltenVk = gd.IsMoltenVk;
+
+ if (isMoltenVk)
+ {
+ UpdateVertexAttributeDescriptions(gd);
+ }
+
fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions = &Internal.VertexAttributeDescriptions[0])
+ fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions2 = &_vertexAttributeDescriptions2[0])
fixed (VertexInputBindingDescription* pVertexBindingDescriptions = &Internal.VertexBindingDescriptions[0])
fixed (Viewport* pViewports = &Internal.Viewports[0])
fixed (Rect2D* pScissors = &Internal.Scissors[0])
@@ -410,7 +429,7 @@ namespace Ryujinx.Graphics.Vulkan
{
SType = StructureType.PipelineVertexInputStateCreateInfo,
VertexAttributeDescriptionCount = VertexAttributeDescriptionsCount,
- PVertexAttributeDescriptions = pVertexAttributeDescriptions,
+ PVertexAttributeDescriptions = isMoltenVk ? pVertexAttributeDescriptions2 : pVertexAttributeDescriptions,
VertexBindingDescriptionCount = VertexBindingDescriptionsCount,
PVertexBindingDescriptions = pVertexBindingDescriptions
};
@@ -471,6 +490,17 @@ namespace Ryujinx.Graphics.Vulkan
PScissors = pScissors
};
+ if (gd.Capabilities.SupportsDepthClipControl)
+ {
+ var viewportDepthClipControlState = new PipelineViewportDepthClipControlCreateInfoEXT()
+ {
+ SType = StructureType.PipelineViewportDepthClipControlCreateInfoExt,
+ NegativeOneToOne = DepthMode
+ };
+
+ viewportState.PNext = &viewportDepthClipControlState;
+ }
+
var multisampleState = new PipelineMultisampleStateCreateInfo
{
SType = StructureType.PipelineMultisampleStateCreateInfo,
@@ -513,6 +543,27 @@ namespace Ryujinx.Graphics.Vulkan
MaxDepthBounds = MaxDepthBounds
};
+ uint blendEnables = 0;
+
+ if (gd.IsMoltenVk && Internal.AttachmentIntegerFormatMask != 0)
+ {
+ // Blend can't be enabled for integer formats, so let's make sure it is disabled.
+ uint attachmentIntegerFormatMask = Internal.AttachmentIntegerFormatMask;
+
+ while (attachmentIntegerFormatMask != 0)
+ {
+ int i = BitOperations.TrailingZeroCount(attachmentIntegerFormatMask);
+
+ if (Internal.ColorBlendAttachmentState[i].BlendEnable)
+ {
+ blendEnables |= 1u << i;
+ }
+
+ Internal.ColorBlendAttachmentState[i].BlendEnable = false;
+ attachmentIntegerFormatMask &= ~(1u << i);
+ }
+ }
+
var colorBlendState = new PipelineColorBlendStateCreateInfo()
{
SType = StructureType.PipelineColorBlendStateCreateInfo,
@@ -590,6 +641,15 @@ namespace Ryujinx.Graphics.Vulkan
};
gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle).ThrowOnError();
+
+ // Restore previous blend enable values if we changed it.
+ while (blendEnables != 0)
+ {
+ int i = BitOperations.TrailingZeroCount(blendEnables);
+
+ Internal.ColorBlendAttachmentState[i].BlendEnable = true;
+ blendEnables &= ~(1u << i);
+ }
}
pipeline = new Auto(new DisposablePipeline(gd.Api, device, pipelineHandle));
@@ -612,6 +672,62 @@ namespace Ryujinx.Graphics.Vulkan
}
}
+ private void UpdateVertexAttributeDescriptions(VulkanRenderer gd)
+ {
+ // Vertex attributes exceeding the stride are invalid.
+ // In metal, they cause glitches with the vertex shader fetching incorrect values.
+ // To work around this, we reduce the format to something that doesn't exceed the stride if possible.
+ // The assumption is that the exceeding components are not actually accessed on the shader.
+
+ for (int index = 0; index < VertexAttributeDescriptionsCount; index++)
+ {
+ var attribute = Internal.VertexAttributeDescriptions[index];
+ int vbIndex = GetVertexBufferIndex(attribute.Binding);
+
+ if (vbIndex >= 0)
+ {
+ ref var vb = ref Internal.VertexBindingDescriptions[vbIndex];
+
+ Format format = attribute.Format;
+
+ while (vb.Stride != 0 && attribute.Offset + FormatTable.GetAttributeFormatSize(format) > vb.Stride)
+ {
+ Format newFormat = FormatTable.DropLastComponent(format);
+
+ if (newFormat == format)
+ {
+ // That case means we failed to find a format that fits within the stride,
+ // so just restore the original format and give up.
+ format = attribute.Format;
+ break;
+ }
+
+ format = newFormat;
+ }
+
+ if (attribute.Format != format && gd.FormatCapabilities.BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, format))
+ {
+ attribute.Format = format;
+ }
+ }
+
+ _vertexAttributeDescriptions2[index] = attribute;
+ }
+ }
+
+ private int GetVertexBufferIndex(uint binding)
+ {
+ for (int index = 0; index < VertexBindingDescriptionsCount; index++)
+ {
+ if (Internal.VertexBindingDescriptions[index].Binding == binding)
+ {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
public void Dispose()
{
Stages.Dispose();
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs
index 78d6e9f71..bf23f4714 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs
@@ -35,6 +35,7 @@ namespace Ryujinx.Graphics.Vulkan
public Array16 Scissors;
public Array8 ColorBlendAttachmentState;
public Array9 AttachmentFormats;
+ public uint AttachmentIntegerFormatMask;
public override bool Equals(object obj)
{
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
index 0582e6ca8..4edb6f2fe 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
@@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
- var usage = GetImageUsageFromFormat(info.Format);
+ var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
var flags = ImageCreateFlags.CreateMutableFormatBit;
@@ -293,7 +293,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
- public static ImageUsageFlags GetImageUsageFromFormat(GAL.Format format)
+ public static ImageUsageFlags GetImageUsage(GAL.Format format, Target target, bool supportsMsStorage)
{
var usage = DefaultUsageFlags;
@@ -306,7 +306,7 @@ namespace Ryujinx.Graphics.Vulkan
usage |= ImageUsageFlags.ColorAttachmentBit;
}
- if (format.IsImageCompatible())
+ if (format.IsImageCompatible() && (supportsMsStorage || !target.IsMultisample()))
{
usage |= ImageUsageFlags.StorageBit;
}
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
index 62d481eb9..c2be74974 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -54,7 +54,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.Textures.Add(this);
var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
- var usage = TextureStorage.GetImageUsageFromFormat(info.Format);
+ var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
var levels = (uint)info.Levels;
var layers = (uint)info.GetLayers();
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
index 499a9ef78..51a3b129a 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
@@ -41,6 +41,7 @@ namespace Ryujinx.Graphics.Vulkan
"VK_EXT_subgroup_size_control",
"VK_NV_geometry_shader_passthrough",
"VK_NV_viewport_array2",
+ "VK_EXT_depth_clip_control",
"VK_KHR_portability_subset" // As per spec, we should enable this if present.
};
@@ -345,6 +346,17 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesRobustness2;
}
+ PhysicalDeviceDepthClipControlFeaturesEXT supportedFeaturesDepthClipControl = new PhysicalDeviceDepthClipControlFeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
+ PNext = features2.PNext
+ };
+
+ if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control"))
+ {
+ features2.PNext = &supportedFeaturesDepthClipControl;
+ }
+
api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2);
var supportedFeatures = features2.Features;
@@ -507,6 +519,21 @@ namespace Ryujinx.Graphics.Vulkan
pExtendedFeatures = &featuresCustomBorderColor;
}
+ PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl;
+
+ if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control") &&
+ supportedFeaturesDepthClipControl.DepthClipControl)
+ {
+ featuresDepthClipControl = new PhysicalDeviceDepthClipControlFeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt,
+ PNext = pExtendedFeatures,
+ DepthClipControl = true
+ };
+
+ pExtendedFeatures = &featuresDepthClipControl;
+ }
+
var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray();
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
index 74267325c..3987be9b4 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -216,6 +216,11 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt
};
+ PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl = new PhysicalDeviceDepthClipControlFeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt
+ };
+
PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new PhysicalDevicePortabilitySubsetFeaturesKHR()
{
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr
@@ -244,6 +249,14 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &featuresCustomBorderColor;
}
+ bool supportsDepthClipControl = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control");
+
+ if (supportsDepthClipControl)
+ {
+ featuresDepthClipControl.PNext = features2.PNext;
+ features2.PNext = &featuresDepthClipControl;
+ }
+
bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset");
if (usePortability)
@@ -295,6 +308,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsSubgroupSizeControl,
featuresShaderInt8.ShaderInt8,
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
+ features2.Features.ShaderStorageImageMultisample,
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
features2.Features.MultiViewport,
@@ -309,6 +323,7 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.PhysicalDeviceFeatures.GeometryShader,
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
+ supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
propertiesSubgroupSizeControl.MinSubgroupSize,
propertiesSubgroupSizeControl.MaxSubgroupSize,
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
@@ -584,6 +599,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsViewportMask: Capabilities.SupportsViewportArray2,
supportsViewportSwizzle: false,
supportsIndirectParameters: true,
+ supportsDepthClipControl: Capabilities.SupportsDepthClipControl,
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage,
maximumTexturesPerStage: Constants.MaxTexturesPerStage,
diff --git a/src/Ryujinx.HLE/HOS/ModLoader.cs b/src/Ryujinx.HLE/HOS/ModLoader.cs
index bde4d11ce..ac983cfe5 100644
--- a/src/Ryujinx.HLE/HOS/ModLoader.cs
+++ b/src/Ryujinx.HLE/HOS/ModLoader.cs
@@ -167,12 +167,12 @@ namespace Ryujinx.HLE.HOS
if (StrEquals(RomfsDir, modDir.Name))
{
- mods.RomfsDirs.Add(mod = new Mod($"<{titleId} RomFs>", modDir));
+ mods.RomfsDirs.Add(mod = new Mod(dir.Name, modDir));
types.Append('R');
}
else if (StrEquals(ExefsDir, modDir.Name))
{
- mods.ExefsDirs.Add(mod = new Mod($"<{titleId} ExeFs>", modDir));
+ mods.ExefsDirs.Add(mod = new Mod(dir.Name, modDir));
types.Append('E');
}
else if (StrEquals(CheatDir, modDir.Name))
diff --git a/src/Ryujinx/Ui/MainWindow.cs b/src/Ryujinx/Ui/MainWindow.cs
index 7cae62227..1918594ce 100644
--- a/src/Ryujinx/Ui/MainWindow.cs
+++ b/src/Ryujinx/Ui/MainWindow.cs
@@ -1024,6 +1024,8 @@ namespace Ryujinx.Ui
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
+
+ appMetadata.LastPlayed = DateTime.UtcNow;
});
}
}
diff --git a/src/Ryujinx/Ui/RendererWidgetBase.cs b/src/Ryujinx/Ui/RendererWidgetBase.cs
index 573b69b35..e2cba7775 100644
--- a/src/Ryujinx/Ui/RendererWidgetBase.cs
+++ b/src/Ryujinx/Ui/RendererWidgetBase.cs
@@ -381,7 +381,7 @@ namespace Ryujinx.Ui
string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png";
string directory = AppDataManager.Mode switch
{
- AppDataManager.LaunchMode.Portable => System.IO.Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
+ AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => System.IO.Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
_ => System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx")
};
diff --git a/src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs b/src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs
index fa1a06578..3edc002d7 100644
--- a/src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs
+++ b/src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs
@@ -48,6 +48,8 @@ namespace Ryujinx.Ui.Windows
private Label _patreonNamesLabel;
private ScrolledWindow _patreonNamesScrolled;
private TextView _patreonNamesText;
+ private EventBox _changelogEventBox;
+ private Label _changelogLinkLabel;
private void InitializeComponent()
{
@@ -148,6 +150,23 @@ namespace Ryujinx.Ui.Windows
Margin = 5
};
+ //
+ // _changelogEventBox
+ //
+ _changelogEventBox = new EventBox();
+ _changelogEventBox.ButtonPressEvent += ChangelogButton_Pressed;
+
+ //
+ // _changelogLinkLabel
+ //
+ _changelogLinkLabel = new Label("View Changelog on GitHub")
+ {
+ TooltipText = "Click to open the changelog for this version in your default browser.",
+ Justify = Justification.Center,
+ Attributes = new AttrList()
+ };
+ _changelogLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
+
//
// _disclaimerLabel
//
@@ -464,8 +483,11 @@ namespace Ryujinx.Ui.Windows
_socialBox.Add(_discordEventBox);
_socialBox.Add(_twitterEventBox);
+ _changelogEventBox.Add(_changelogLinkLabel);
+
_leftBox.Add(_logoBox);
_leftBox.Add(_versionLabel);
+ _leftBox.Add(_changelogEventBox);
_leftBox.Add(_disclaimerLabel);
_leftBox.Add(_amiiboApiLink);
_leftBox.Add(_socialBox);
diff --git a/src/Ryujinx/Ui/Windows/AboutWindow.cs b/src/Ryujinx/Ui/Windows/AboutWindow.cs
index 41cf9c013..15bfa500d 100644
--- a/src/Ryujinx/Ui/Windows/AboutWindow.cs
+++ b/src/Ryujinx/Ui/Windows/AboutWindow.cs
@@ -76,5 +76,10 @@ namespace Ryujinx.Ui.Windows
{
OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
}
+
+ private void ChangelogButton_Pressed(object sender, ButtonPressEventArgs args)
+ {
+ OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog");
+ }
}
}
\ No newline at end of file