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