mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-01-13 06:09:11 +00:00
Merge branch 'master' into fix_unnecessary_dylib
This commit is contained in:
commit
70fb06f4f2
67 changed files with 1261 additions and 701 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
|
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 35
|
timeout-minutes: 45
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
|
@ -110,7 +110,7 @@ jobs:
|
||||||
build_macos:
|
build_macos:
|
||||||
name: macOS Universal (${{ matrix.configuration }})
|
name: macOS Universal (${{ matrix.configuration }})
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 35
|
timeout-minutes: 45
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
configuration: [ Debug, Release ]
|
configuration: [ Debug, Release ]
|
||||||
|
|
2
.github/workflows/flatpak.yml
vendored
2
.github/workflows/flatpak.yml
vendored
|
@ -12,7 +12,7 @@ concurrency: flatpak-release
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
timeout-minutes: 35
|
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|
2
.github/workflows/nightly_pr_comment.yml
vendored
2
.github/workflows/nightly_pr_comment.yml
vendored
|
@ -7,7 +7,7 @@ jobs:
|
||||||
pr_comment:
|
pr_comment:
|
||||||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 35
|
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
|
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Create tag
|
- name: Create tag
|
||||||
uses: actions/github-script@v5
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.git.createRef({
|
github.rest.git.createRef({
|
||||||
|
@ -46,7 +46,7 @@ jobs:
|
||||||
release:
|
release:
|
||||||
name: Release ${{ matrix.OS_NAME }}
|
name: Release ${{ matrix.OS_NAME }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 35
|
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-latest, windows-latest ]
|
os: [ ubuntu-latest, windows-latest ]
|
||||||
|
@ -144,7 +144,7 @@ jobs:
|
||||||
macos_release:
|
macos_release:
|
||||||
name: Release MacOS universal
|
name: Release MacOS universal
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 35
|
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia" Version="0.10.19" />
|
<PackageVersion Include="Avalonia" Version="0.10.21" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.21" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
|
<PackageVersion Include="Avalonia.Desktop" Version="0.10.21" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.21" />
|
||||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
|
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.21" />
|
||||||
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
|
||||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
|
|
|
@ -11,4 +11,10 @@ if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
|
||||||
RYUJINX_BIN="Ryujinx.Headless.SDL2"
|
RYUJINX_BIN="Ryujinx.Headless.SDL2"
|
||||||
fi
|
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" "$@"
|
||||||
|
|
|
@ -25,14 +25,27 @@ error_handler() {
|
||||||
exit 1
|
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
|
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.
|
# Now replace and reopen.
|
||||||
rm -rf "$INSTALL_DIRECTORY"
|
rm -rf "$INSTALL_DIRECTORY"
|
||||||
mv "$NEW_APP_DIRECTORY" "$INSTALL_DIRECTORY"
|
mv "$NEW_APP_DIRECTORY" "$INSTALL_DIRECTORY"
|
||||||
|
|
|
@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_supportSurroundConfiguration = spec.channels == 6;
|
_supportSurroundConfiguration = spec.channels >= 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
private AudioRendererRenderingDevice _renderingDevice;
|
private AudioRendererRenderingDevice _renderingDevice;
|
||||||
private AudioRendererExecutionMode _executionMode;
|
private AudioRendererExecutionMode _executionMode;
|
||||||
private IWritableEvent _systemEvent;
|
private IWritableEvent _systemEvent;
|
||||||
private ManualResetEvent _terminationEvent;
|
|
||||||
private MemoryPoolState _dspMemoryPoolState;
|
private MemoryPoolState _dspMemoryPoolState;
|
||||||
private VoiceContext _voiceContext;
|
private VoiceContext _voiceContext;
|
||||||
private MixContext _mixContext;
|
private MixContext _mixContext;
|
||||||
|
@ -83,7 +82,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent)
|
public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent)
|
||||||
{
|
{
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_terminationEvent = new ManualResetEvent(false);
|
|
||||||
_dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp);
|
_dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp);
|
||||||
_voiceContext = new VoiceContext();
|
_voiceContext = new VoiceContext();
|
||||||
_mixContext = new MixContext();
|
_mixContext = new MixContext();
|
||||||
|
@ -387,11 +385,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_executionMode == AudioRendererExecutionMode.Auto)
|
|
||||||
{
|
|
||||||
_terminationEvent.WaitOne();
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}");
|
Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,8 +661,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
if (_isActive)
|
if (_isActive)
|
||||||
{
|
{
|
||||||
_terminationEvent.Reset();
|
|
||||||
|
|
||||||
if (!_manager.Processor.HasRemainingCommands(_sessionId))
|
if (!_manager.Processor.HasRemainingCommands(_sessionId))
|
||||||
{
|
{
|
||||||
GenerateCommandList(out CommandList commands);
|
GenerateCommandList(out CommandList commands);
|
||||||
|
@ -686,10 +677,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
_isDspRunningBehind = true;
|
_isDspRunningBehind = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_terminationEvent.Set();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,7 +844,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
_manager.Unregister(this);
|
_manager.Unregister(this);
|
||||||
_terminationEvent.Dispose();
|
|
||||||
_workBufferMemoryPin.Dispose();
|
_workBufferMemoryPin.Dispose();
|
||||||
|
|
||||||
if (MemoryManager is IRefCounted rc)
|
if (MemoryManager is IRefCounted rc)
|
||||||
|
|
|
@ -270,7 +270,7 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
string directory = AppDataManager.Mode switch
|
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")
|
_ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -637,5 +637,7 @@
|
||||||
"SettingsTabNetworkInterface": "Network Interface:",
|
"SettingsTabNetworkInterface": "Network Interface:",
|
||||||
"NetworkInterfaceTooltip": "The network interface used for LAN features",
|
"NetworkInterfaceTooltip": "The network interface used for LAN features",
|
||||||
"NetworkInterfaceDefault": "Default",
|
"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."
|
||||||
}
|
}
|
|
@ -3,21 +3,20 @@ using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Notifications;
|
using Avalonia.Controls.Notifications;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
namespace Ryujinx.Ava.UI.Helpers
|
||||||
{
|
{
|
||||||
public static class NotificationHelper
|
public static class NotificationHelper
|
||||||
{
|
{
|
||||||
private const int MaxNotifications = 4;
|
private const int MaxNotifications = 4;
|
||||||
private const int NotificationDelayInMs = 5000;
|
private const int NotificationDelayInMs = 5000;
|
||||||
|
|
||||||
private static WindowNotificationManager _notificationManager;
|
private static WindowNotificationManager _notificationManager;
|
||||||
|
|
||||||
private static readonly ManualResetEvent _templateAppliedEvent = new(false);
|
|
||||||
private static readonly BlockingCollection<Notification> _notifications = new();
|
private static readonly BlockingCollection<Notification> _notifications = new();
|
||||||
|
|
||||||
public static void SetNotificationManager(Window host)
|
public static void SetNotificationManager(Window host)
|
||||||
|
@ -29,25 +28,31 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||||
Margin = new Thickness(0, 0, 15, 40)
|
Margin = new Thickness(0, 0, 15, 40)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var maybeAsyncWorkQueue = new Lazy<AsyncWorkQueue<Notification>>(
|
||||||
|
() => new AsyncWorkQueue<Notification>(notification =>
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
_notificationManager.Show(notification);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"UI.NotificationThread",
|
||||||
|
_notifications),
|
||||||
|
LazyThreadSafetyMode.ExecutionAndPublication);
|
||||||
|
|
||||||
_notificationManager.TemplateApplied += (sender, args) =>
|
_notificationManager.TemplateApplied += (sender, args) =>
|
||||||
{
|
{
|
||||||
_templateAppliedEvent.Set();
|
// NOTE: Force creation of the AsyncWorkQueue.
|
||||||
|
_ = maybeAsyncWorkQueue.Value;
|
||||||
};
|
};
|
||||||
|
|
||||||
Task.Run(async () =>
|
host.Closing += (sender, args) =>
|
||||||
{
|
{
|
||||||
_templateAppliedEvent.WaitOne();
|
if (maybeAsyncWorkQueue.IsValueCreated)
|
||||||
|
|
||||||
foreach (var notification in _notifications.GetConsumingEnumerable())
|
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
maybeAsyncWorkQueue.Value.Dispose();
|
||||||
{
|
|
||||||
_notificationManager.Show(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
await Task.Delay(NotificationDelayInMs / MaxNotifications);
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null)
|
public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null)
|
||||||
|
|
|
@ -1529,6 +1529,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
|
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
|
||||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appMetadata.LastPlayed = DateTime.UtcNow;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,18 @@
|
||||||
LineHeight="12"
|
LineHeight="12"
|
||||||
Text="{Binding Version}"
|
Text="{Binding Version}"
|
||||||
TextAlignment="Center" />
|
TextAlignment="Center" />
|
||||||
|
<Button
|
||||||
|
Padding="5"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Background="Transparent"
|
||||||
|
Click="Button_OnClick"
|
||||||
|
Tag="https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog">
|
||||||
|
<TextBlock
|
||||||
|
FontSize="10"
|
||||||
|
Text="{locale:Locale AboutChangelogButton}"
|
||||||
|
TextAlignment="Center"
|
||||||
|
ToolTip.Tip="{locale:Locale AboutChangelogButtonTooltipMessage}" />
|
||||||
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
|
|
@ -22,9 +22,11 @@ namespace Ryujinx.Common
|
||||||
_cts = new CancellationTokenSource();
|
_cts = new CancellationTokenSource();
|
||||||
_queue = collection;
|
_queue = collection;
|
||||||
_workerAction = callback;
|
_workerAction = callback;
|
||||||
_workerThread = new Thread(DoWork) { Name = name };
|
_workerThread = new Thread(DoWork)
|
||||||
|
{
|
||||||
_workerThread.IsBackground = true;
|
Name = name,
|
||||||
|
IsBackground = true
|
||||||
|
};
|
||||||
_workerThread.Start();
|
_workerThread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
public readonly bool SupportsViewportMask;
|
public readonly bool SupportsViewportMask;
|
||||||
public readonly bool SupportsViewportSwizzle;
|
public readonly bool SupportsViewportSwizzle;
|
||||||
public readonly bool SupportsIndirectParameters;
|
public readonly bool SupportsIndirectParameters;
|
||||||
|
public readonly bool SupportsDepthClipControl;
|
||||||
|
|
||||||
public readonly uint MaximumUniformBuffersPerStage;
|
public readonly uint MaximumUniformBuffersPerStage;
|
||||||
public readonly uint MaximumStorageBuffersPerStage;
|
public readonly uint MaximumStorageBuffersPerStage;
|
||||||
|
@ -85,6 +86,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
bool supportsViewportMask,
|
bool supportsViewportMask,
|
||||||
bool supportsViewportSwizzle,
|
bool supportsViewportSwizzle,
|
||||||
bool supportsIndirectParameters,
|
bool supportsIndirectParameters,
|
||||||
|
bool supportsDepthClipControl,
|
||||||
uint maximumUniformBuffersPerStage,
|
uint maximumUniformBuffersPerStage,
|
||||||
uint maximumStorageBuffersPerStage,
|
uint maximumStorageBuffersPerStage,
|
||||||
uint maximumTexturesPerStage,
|
uint maximumTexturesPerStage,
|
||||||
|
@ -127,6 +129,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
SupportsViewportMask = supportsViewportMask;
|
SupportsViewportMask = supportsViewportMask;
|
||||||
SupportsViewportSwizzle = supportsViewportSwizzle;
|
SupportsViewportSwizzle = supportsViewportSwizzle;
|
||||||
SupportsIndirectParameters = supportsIndirectParameters;
|
SupportsIndirectParameters = supportsIndirectParameters;
|
||||||
|
SupportsDepthClipControl = supportsDepthClipControl;
|
||||||
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
|
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
|
||||||
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
|
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
|
||||||
MaximumTexturesPerStage = maximumTexturesPerStage;
|
MaximumTexturesPerStage = maximumTexturesPerStage;
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
private const ushort FileFormatVersionMajor = 1;
|
private const ushort FileFormatVersionMajor = 1;
|
||||||
private const ushort FileFormatVersionMinor = 2;
|
private const ushort FileFormatVersionMinor = 2;
|
||||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||||
private const uint CodeGenVersion = 4646;
|
private const uint CodeGenVersion = 5027;
|
||||||
|
|
||||||
private const string SharedTocFileName = "shared.toc";
|
private const string SharedTocFileName = "shared.toc";
|
||||||
private const string SharedDataFileName = "shared.data";
|
private const string SharedDataFileName = "shared.data";
|
||||||
|
|
|
@ -165,6 +165,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
public bool QueryHostSupportsViewportMask() => _context.Capabilities.SupportsViewportMask;
|
public bool QueryHostSupportsViewportMask() => _context.Capabilities.SupportsViewportMask;
|
||||||
|
|
||||||
|
public bool QueryHostSupportsDepthClipControl() => _context.Capabilities.SupportsDepthClipControl;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a packed Maxwell texture format to the shader translator texture format.
|
/// Converts a packed Maxwell texture format to the shader translator texture format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -163,6 +163,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
|
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
|
||||||
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
||||||
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
|
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
|
||||||
|
supportsDepthClipControl: true,
|
||||||
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
|
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
|
||||||
maximumStorageBuffersPerStage: 16,
|
maximumStorageBuffersPerStage: 16,
|
||||||
maximumTexturesPerStage: 32,
|
maximumTexturesPerStage: 32,
|
||||||
|
|
|
@ -239,33 +239,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||||
context.AppendLine();
|
context.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isFragment = context.Config.Stage == ShaderStage.Fragment;
|
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryEarlyZForce())
|
||||||
|
|
||||||
if (isFragment || context.Config.Stage == ShaderStage.Compute || context.Config.Stage == ShaderStage.Vertex)
|
|
||||||
{
|
{
|
||||||
if (isFragment && context.Config.GpuAccessor.QueryEarlyZForce())
|
context.AppendLine("layout(early_fragment_tests) in;");
|
||||||
{
|
context.AppendLine();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.AtomicMinMaxS32Shared) != 0)
|
if ((info.HelperFunctionsMask & HelperFunctionsMask.AtomicMinMaxS32Shared) != 0)
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -101,6 +101,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
Add(Instruction.MemoryBarrier, InstType.CallNullary, "memoryBarrier");
|
Add(Instruction.MemoryBarrier, InstType.CallNullary, "memoryBarrier");
|
||||||
Add(Instruction.Minimum, InstType.CallBinary, "min");
|
Add(Instruction.Minimum, InstType.CallBinary, "min");
|
||||||
Add(Instruction.MinimumU32, InstType.CallBinary, "min");
|
Add(Instruction.MinimumU32, InstType.CallBinary, "min");
|
||||||
|
Add(Instruction.Modulo, InstType.CallBinary, "mod");
|
||||||
Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1);
|
Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1);
|
||||||
Add(Instruction.MultiplyHighS32, InstType.CallBinary, HelperFunctionNames.MultiplyHighS32);
|
Add(Instruction.MultiplyHighS32, InstType.CallBinary, HelperFunctionNames.MultiplyHighS32);
|
||||||
Add(Instruction.MultiplyHighU32, InstType.CallBinary, HelperFunctionNames.MultiplyHighU32);
|
Add(Instruction.MultiplyHighU32, InstType.CallBinary, HelperFunctionNames.MultiplyHighU32);
|
||||||
|
|
|
@ -97,30 +97,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
texCallBuilder.Append(str);
|
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)
|
if (pCount > 1)
|
||||||
{
|
{
|
||||||
string[] elems = new string[pCount];
|
string[] elems = new string[pCount];
|
||||||
|
@ -130,7 +106,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
elems[index] = Src(AggregateType.S32);
|
elems[index] = Src(AggregateType.S32);
|
||||||
}
|
}
|
||||||
|
|
||||||
Append(ApplyScaling($"ivec{pCount}({string.Join(", ", elems)})"));
|
Append($"ivec{pCount}({string.Join(", ", elems)})");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -584,53 +560,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string ApplyScaling(string vector)
|
Append(AssemblePVector(pCount));
|
||||||
{
|
|
||||||
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))));
|
|
||||||
|
|
||||||
string AssembleDerivativesVector(int count)
|
string AssembleDerivativesVector(int count)
|
||||||
{
|
{
|
||||||
|
@ -750,7 +680,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
}
|
}
|
||||||
else
|
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;
|
bool hasLod = !descriptor.Type.HasFlag(SamplerType.Multisample) && descriptor.Type != SamplerType.TextureBuffer;
|
||||||
string texCall;
|
string texCall;
|
||||||
|
|
||||||
|
@ -767,14 +697,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
texCall = $"textureSize({samplerName}){GetMask(texOp.Index)}";
|
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;
|
return texCall;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
|
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
|
||||||
|
|
||||||
public Instruction CoordTemp { get; set; }
|
public Instruction CoordTemp { get; set; }
|
||||||
|
public StructuredFunction CurrentFunction { get; set; }
|
||||||
private readonly Dictionary<AstOperand, Instruction> _locals = new Dictionary<AstOperand, Instruction>();
|
private readonly Dictionary<AstOperand, Instruction> _locals = new Dictionary<AstOperand, Instruction>();
|
||||||
private readonly Dictionary<int, Instruction[]> _localForArgs = new Dictionary<int, Instruction[]>();
|
private readonly Dictionary<int, Instruction[]> _localForArgs = new Dictionary<int, Instruction[]>();
|
||||||
private readonly Dictionary<int, Instruction> _funcArgs = new Dictionary<int, Instruction>();
|
private readonly Dictionary<int, Instruction> _funcArgs = new Dictionary<int, Instruction>();
|
||||||
|
|
|
@ -4,7 +4,6 @@ using Ryujinx.Graphics.Shader.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using static Spv.Specification;
|
using static Spv.Specification;
|
||||||
|
|
||||||
|
@ -114,6 +113,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
Add(Instruction.MemoryBarrier, GenerateMemoryBarrier);
|
Add(Instruction.MemoryBarrier, GenerateMemoryBarrier);
|
||||||
Add(Instruction.Minimum, GenerateMinimum);
|
Add(Instruction.Minimum, GenerateMinimum);
|
||||||
Add(Instruction.MinimumU32, GenerateMinimumU32);
|
Add(Instruction.MinimumU32, GenerateMinimumU32);
|
||||||
|
Add(Instruction.Modulo, GenerateModulo);
|
||||||
Add(Instruction.Multiply, GenerateMultiply);
|
Add(Instruction.Multiply, GenerateMultiply);
|
||||||
Add(Instruction.MultiplyHighS32, GenerateMultiplyHighS32);
|
Add(Instruction.MultiplyHighS32, GenerateMultiplyHighS32);
|
||||||
Add(Instruction.MultiplyHighU32, GenerateMultiplyHighU32);
|
Add(Instruction.MultiplyHighU32, GenerateMultiplyHighU32);
|
||||||
|
@ -744,8 +744,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
pCoords = Src(AggregateType.S32);
|
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 imageType, var imageVariable) = context.Images[new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format)];
|
||||||
|
|
||||||
var image = context.Load(imageType, imageVariable);
|
var image = context.Load(imageType, imageVariable);
|
||||||
|
@ -1040,6 +1038,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
return GenerateBinaryU32(context, operation, context.Delegates.GlslUMin);
|
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)
|
private static OperationResult GenerateMultiply(CodeGenContext context, AstOperation operation)
|
||||||
{
|
{
|
||||||
return GenerateBinary(context, operation, context.Delegates.FMul, context.Delegates.IMul);
|
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)
|
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;
|
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);
|
SpvInstruction pCoords = AssemblePVector(pCount);
|
||||||
pCoords = ScalingHelpers.ApplyScaling(context, texOp, pCoords, intCoords, isBindless, isIndexed, isArray, pCount);
|
|
||||||
|
|
||||||
SpvInstruction AssembleDerivativesVector(int count)
|
SpvInstruction AssembleDerivativesVector(int count)
|
||||||
{
|
{
|
||||||
|
@ -1638,8 +1621,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
image = context.Image(imageType, image);
|
image = context.Image(imageType, image);
|
||||||
}
|
}
|
||||||
|
|
||||||
pCoords = ApplyBias(pCoords, image);
|
|
||||||
|
|
||||||
var operands = operandsList.ToArray();
|
var operands = operandsList.ToArray();
|
||||||
|
|
||||||
SpvInstruction result;
|
SpvInstruction result;
|
||||||
|
@ -1755,11 +1736,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
result = context.CompositeExtract(context.TypeS32(), result, (SpvLiteralInteger)texOp.Index);
|
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);
|
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));
|
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);
|
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));
|
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);
|
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));
|
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);
|
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));
|
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);
|
context.Decorate(result, Decoration.NoContraction);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -67,6 +67,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
public readonly FuncBinaryInstruction GlslSMax;
|
public readonly FuncBinaryInstruction GlslSMax;
|
||||||
public readonly FuncBinaryInstruction GlslFMin;
|
public readonly FuncBinaryInstruction GlslFMin;
|
||||||
public readonly FuncBinaryInstruction GlslSMin;
|
public readonly FuncBinaryInstruction GlslSMin;
|
||||||
|
public readonly FuncBinaryInstruction FMod;
|
||||||
public readonly FuncBinaryInstruction FMul;
|
public readonly FuncBinaryInstruction FMul;
|
||||||
public readonly FuncBinaryInstruction IMul;
|
public readonly FuncBinaryInstruction IMul;
|
||||||
public readonly FuncBinaryInstruction FSub;
|
public readonly FuncBinaryInstruction FSub;
|
||||||
|
@ -174,6 +175,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
GlslSMax = context.GlslSMax;
|
GlslSMax = context.GlslSMax;
|
||||||
GlslFMin = context.GlslFMin;
|
GlslFMin = context.GlslFMin;
|
||||||
GlslSMin = context.GlslSMin;
|
GlslSMin = context.GlslSMin;
|
||||||
|
FMod = context.FMod;
|
||||||
FMul = context.FMul;
|
FMul = context.FMul;
|
||||||
IMul = context.IMul;
|
IMul = context.IMul;
|
||||||
FSub = context.FSub;
|
FSub = context.FSub;
|
||||||
|
|
|
@ -144,10 +144,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
|
|
||||||
private static void Generate(CodeGenContext context, StructuredProgramInfo info, int funcIndex)
|
private static void Generate(CodeGenContext context, StructuredProgramInfo info, int funcIndex)
|
||||||
{
|
{
|
||||||
var function = info.Functions[funcIndex];
|
(var function, var spvFunc) = context.GetFunction(funcIndex);
|
||||||
|
|
||||||
(_, var spvFunc) = context.GetFunction(funcIndex);
|
|
||||||
|
|
||||||
|
context.CurrentFunction = function;
|
||||||
context.AddFunction(spvFunc);
|
context.AddFunction(spvFunc);
|
||||||
context.StartFunction();
|
context.StartFunction();
|
||||||
|
|
||||||
|
|
|
@ -367,6 +367,15 @@ namespace Ryujinx.Graphics.Shader
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries whether the host supports depth clip control.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the GPU and driver supports depth clip control, false otherwise</returns>
|
||||||
|
bool QueryHostSupportsDepthClipControl()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries the point size from the GPU state, used when it is not explicitly set on the shader.
|
/// Queries the point size from the GPU state, used when it is not explicitly set on the shader.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -187,27 +187,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
context.Config.GpuAccessor.Log("Shader instruction Longjmp is not implemented.");
|
context.Config.GpuAccessor.Log("Shader instruction Longjmp is not implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void P2rR(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstP2rR op = context.GetOp<InstP2rR>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction P2rR is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void P2rI(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstP2rI op = context.GetOp<InstP2rI>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction P2rI is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void P2rC(EmitterContext context)
|
|
||||||
{
|
|
||||||
InstP2rC op = context.GetOp<InstP2rC>();
|
|
||||||
|
|
||||||
context.Config.GpuAccessor.Log("Shader instruction P2rC is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Pexit(EmitterContext context)
|
public static void Pexit(EmitterContext context)
|
||||||
{
|
{
|
||||||
InstPexit op = context.GetOp<InstPexit>();
|
InstPexit op = context.GetOp<InstPexit>();
|
||||||
|
|
|
@ -209,21 +209,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
return context.ICompareNotEqual(context.BitwiseAnd(value, Const(1 << bit)), Const(0));
|
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.
|
Operand flag = Register(bit, type);
|
||||||
context.Config.GpuAccessor.Log("R2P.CC not implemented.");
|
Operand res = context.ConditionalSelect(Test(mask, bit), Test(value, bit + shift), flag);
|
||||||
}
|
context.Copy(flag, res);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using Ryujinx.Graphics.Shader.Decoders;
|
using Ryujinx.Graphics.Shader.Decoders;
|
||||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
using Ryujinx.Graphics.Shader.Translation;
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
|
|
||||||
using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper;
|
using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper;
|
||||||
using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
|
using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
|
||||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
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.DestPred, RegisterType.Predicate), p0Res);
|
||||||
context.Copy(Register(op.DestPredInv, RegisterType.Predicate), p1Res);
|
context.Copy(Register(op.DestPredInv, RegisterType.Predicate), p1Res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void P2rC(EmitterContext context)
|
||||||
|
{
|
||||||
|
InstP2rC op = context.GetOp<InstP2rC>();
|
||||||
|
|
||||||
|
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<InstP2rI>();
|
||||||
|
|
||||||
|
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<InstP2rR>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -97,6 +97,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||||
MemoryBarrier,
|
MemoryBarrier,
|
||||||
Minimum,
|
Minimum,
|
||||||
MinimumU32,
|
MinimumU32,
|
||||||
|
Modulo,
|
||||||
Multiply,
|
Multiply,
|
||||||
MultiplyHighS32,
|
MultiplyHighS32,
|
||||||
MultiplyHighU32,
|
MultiplyHighU32,
|
||||||
|
|
|
@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||||
public Instruction Inst { get; private set; }
|
public Instruction Inst { get; private set; }
|
||||||
public StorageKind StorageKind { get; }
|
public StorageKind StorageKind { get; }
|
||||||
|
|
||||||
|
public bool ForcePrecise { get; set; }
|
||||||
|
|
||||||
private Operand[] _dests;
|
private Operand[] _dests;
|
||||||
|
|
||||||
public Operand Dest
|
public Operand Dest
|
||||||
|
|
|
@ -4,10 +4,6 @@
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Remove="CodeGen\Glsl\HelperFunctions\TexelFetchScale_vp.glsl" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
<ProjectReference Include="..\Spv.Generator\Spv.Generator.csproj" />
|
<ProjectReference Include="..\Spv.Generator\Spv.Generator.csproj" />
|
||||||
|
@ -25,9 +21,6 @@
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\StoreSharedSmallInt.glsl" />
|
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\StoreSharedSmallInt.glsl" />
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\StoreStorageSmallInt.glsl" />
|
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\StoreStorageSmallInt.glsl" />
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" />
|
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" />
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\TexelFetchScale_vp.glsl" />
|
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\TexelFetchScale_fp.glsl" />
|
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\TexelFetchScale_cp.glsl" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
public Instruction Inst { get; }
|
public Instruction Inst { get; }
|
||||||
public StorageKind StorageKind { get; }
|
public StorageKind StorageKind { get; }
|
||||||
|
public bool ForcePrecise { get; }
|
||||||
|
|
||||||
public int Index { get; }
|
public int Index { get; }
|
||||||
|
|
||||||
|
@ -17,10 +18,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
|
|
||||||
public int SourcesCount => _sources.Length;
|
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;
|
Inst = inst;
|
||||||
StorageKind = storageKind;
|
StorageKind = storageKind;
|
||||||
|
ForcePrecise = forcePrecise;
|
||||||
_sources = sources;
|
_sources = sources;
|
||||||
|
|
||||||
for (int index = 0; index < sources.Length; index++)
|
for (int index = 0; index < sources.Length; index++)
|
||||||
|
@ -38,12 +40,18 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
Index = 0;
|
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;
|
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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
int cbufSlot,
|
int cbufSlot,
|
||||||
int handle,
|
int handle,
|
||||||
int index,
|
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;
|
Type = type;
|
||||||
Format = format;
|
Format = format;
|
||||||
|
@ -27,10 +27,5 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
CbufSlot = cbufSlot;
|
CbufSlot = cbufSlot;
|
||||||
Handle = handle;
|
Handle = handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AstTextureOperation WithType(SamplerType type)
|
|
||||||
{
|
|
||||||
return new AstTextureOperation(Inst, type, Format, Flags, CbufSlot, Handle, Index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -104,6 +104,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
Add(Instruction.MaximumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
|
Add(Instruction.MaximumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
|
||||||
Add(Instruction.Minimum, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
|
Add(Instruction.Minimum, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
|
||||||
Add(Instruction.MinimumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
|
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.Multiply, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
|
||||||
Add(Instruction.MultiplyHighS32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
|
Add(Instruction.MultiplyHighS32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
|
||||||
Add(Instruction.MultiplyHighU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
|
Add(Instruction.MultiplyHighU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
|
||||||
|
|
|
@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
{
|
{
|
||||||
static class StructuredProgram
|
static class StructuredProgram
|
||||||
{
|
{
|
||||||
public static StructuredProgramInfo MakeStructuredProgram(Function[] functions, ShaderConfig config)
|
public static StructuredProgramInfo MakeStructuredProgram(IReadOnlyList<Function> functions, ShaderConfig config)
|
||||||
{
|
{
|
||||||
StructuredProgramContext context = new StructuredProgramContext(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];
|
Function function = functions[funcIndex];
|
||||||
|
|
||||||
|
@ -156,7 +156,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
}
|
}
|
||||||
else
|
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;
|
AggregateType destElemType = destType;
|
||||||
|
@ -179,7 +185,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
|
|
||||||
dest.VarType = destElemType;
|
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)
|
else if (operation.Dest != null)
|
||||||
|
@ -227,7 +233,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
}
|
}
|
||||||
else if (!isCopy)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -248,7 +260,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
}
|
}
|
||||||
else
|
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,
|
// Those instructions needs to be emulated by using helper functions,
|
||||||
|
|
|
@ -319,7 +319,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||||
new AstOperand(OperandType.Constant, elemIndex)
|
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);
|
return GetOperand(operand);
|
||||||
|
|
|
@ -49,13 +49,17 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
private readonly List<Operation> _operations;
|
private readonly List<Operation> _operations;
|
||||||
private readonly Dictionary<ulong, BlockLabel> _labels;
|
private readonly Dictionary<ulong, BlockLabel> _labels;
|
||||||
|
|
||||||
public EmitterContext(DecodedProgram program, ShaderConfig config, bool isNonMain)
|
public EmitterContext()
|
||||||
|
{
|
||||||
|
_operations = new List<Operation>();
|
||||||
|
_labels = new Dictionary<ulong, BlockLabel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmitterContext(DecodedProgram program, ShaderConfig config, bool isNonMain) : this()
|
||||||
{
|
{
|
||||||
Program = program;
|
Program = program;
|
||||||
Config = config;
|
Config = config;
|
||||||
IsNonMain = isNonMain;
|
IsNonMain = isNonMain;
|
||||||
_operations = new List<Operation>();
|
|
||||||
_labels = new Dictionary<ulong, BlockLabel>();
|
|
||||||
|
|
||||||
EmitStart();
|
EmitStart();
|
||||||
}
|
}
|
||||||
|
@ -242,7 +246,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
|
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 z = this.Load(StorageKind.Output, IoVariable.Position, null, Const(2));
|
||||||
Operand w = this.Load(StorageKind.Output, IoVariable.Position, null, Const(3));
|
Operand w = this.Load(StorageKind.Output, IoVariable.Position, null, Const(3));
|
||||||
|
@ -279,7 +283,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
oldYLocal = null;
|
oldYLocal = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.Options.TargetApi == TargetApi.Vulkan && Config.GpuAccessor.QueryTransformDepthMinusOneToOne())
|
if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne() && !Config.GpuAccessor.QueryHostSupportsDepthClipControl())
|
||||||
{
|
{
|
||||||
oldZLocal = Local();
|
oldZLocal = Local();
|
||||||
this.Copy(oldZLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(2)));
|
this.Copy(oldZLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(2)));
|
||||||
|
|
|
@ -307,6 +307,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return context.Add(fpType | Instruction.Minimum, Local(), a, b);
|
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)
|
public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32)
|
||||||
{
|
{
|
||||||
return context.Add(fpType | Instruction.Multiply, Local(), a, b);
|
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)
|
public static void Return(this EmitterContext context, Operand returnValue)
|
||||||
{
|
{
|
||||||
context.PrepareForReturn();
|
|
||||||
context.Add(Instruction.Return, null, returnValue);
|
context.Add(Instruction.Return, null, returnValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
134
src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs
Normal file
134
src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs
Normal file
|
@ -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<Function> _functionList;
|
||||||
|
private readonly Dictionary<HelperFunctionName, int> _functionIds;
|
||||||
|
private readonly ShaderStage _stage;
|
||||||
|
|
||||||
|
public HelperFunctionManager(List<Function> functionList, ShaderStage stage)
|
||||||
|
{
|
||||||
|
_functionList = functionList;
|
||||||
|
_functionIds = new Dictionary<HelperFunctionName, int>();
|
||||||
|
_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
{
|
||||||
|
enum HelperFunctionName
|
||||||
|
{
|
||||||
|
TexelFetchScale,
|
||||||
|
TextureSizeUnscale
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,9 +11,10 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
{
|
{
|
||||||
static class Rewriter
|
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 isVertexShader = config.Stage == ShaderStage.Vertex;
|
||||||
|
bool isImpreciseFragmentShader = config.Stage == ShaderStage.Fragment && config.GpuAccessor.QueryHostReducedPrecision();
|
||||||
bool hasConstantBufferDrawParameters = config.GpuAccessor.QueryHasConstantBufferDrawParameters();
|
bool hasConstantBufferDrawParameters = config.GpuAccessor.QueryHasConstantBufferDrawParameters();
|
||||||
bool hasVectorIndexingBug = config.GpuAccessor.QueryHostHasVectorIndexingBug();
|
bool hasVectorIndexingBug = config.GpuAccessor.QueryHostHasVectorIndexingBug();
|
||||||
bool supportsSnormBufferTextureFormat = config.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat();
|
bool supportsSnormBufferTextureFormat = config.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat();
|
||||||
|
@ -45,6 +46,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isImpreciseFragmentShader)
|
||||||
|
{
|
||||||
|
EnableForcePreciseIfNeeded(operation);
|
||||||
|
}
|
||||||
|
|
||||||
if (hasVectorIndexingBug)
|
if (hasVectorIndexingBug)
|
||||||
{
|
{
|
||||||
InsertVectorComponentSelect(node, config);
|
InsertVectorComponentSelect(node, config);
|
||||||
|
@ -54,9 +60,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
if (operation is TextureOperation texOp)
|
if (operation is TextureOperation texOp)
|
||||||
{
|
{
|
||||||
|
node = InsertTexelFetchScale(hfm, node, config);
|
||||||
|
node = InsertTextureSizeUnscale(hfm, node, config);
|
||||||
|
|
||||||
if (texOp.Inst == Instruction.TextureSample)
|
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)
|
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<INode> node, ShaderConfig config)
|
private static void InsertVectorComponentSelect(LinkedListNode<INode> node, ShaderConfig config)
|
||||||
{
|
{
|
||||||
Operation operation = (Operation)node.Value;
|
Operation operation = (Operation)node.Value;
|
||||||
|
@ -344,10 +374,279 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LinkedListNode<INode> RewriteTextureSample(LinkedListNode<INode> node, ShaderConfig config)
|
private static LinkedListNode<INode> InsertTexelFetchScale(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
|
||||||
{
|
{
|
||||||
TextureOperation texOp = (TextureOperation)node.Value;
|
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<INode> InsertTextureSizeUnscale(HelperFunctionManager hfm, LinkedListNode<INode> 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<INode> InsertCoordNormalization(LinkedListNode<INode> 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<INode> InsertCoordGatherBias(LinkedListNode<INode> 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<INode> InsertConstOffsets(LinkedListNode<INode> 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 hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
|
||||||
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 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 isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||||
|
|
||||||
bool isCoordNormalized = isBindless || config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot);
|
if (!hasInvalidOffset)
|
||||||
|
|
||||||
if (!hasInvalidOffset && isCoordNormalized)
|
|
||||||
{
|
{
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -454,7 +751,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
hasInvalidOffset &= !areAllOffsetsConstant;
|
hasInvalidOffset &= !areAllOffsetsConstant;
|
||||||
|
|
||||||
if (!hasInvalidOffset && isCoordNormalized)
|
if (!hasInvalidOffset)
|
||||||
{
|
{
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -473,63 +770,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
int componentIndex = texOp.Index;
|
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];
|
Operand[] dests = new Operand[texOp.DestsCount];
|
||||||
|
|
||||||
for (int i = 0; i < texOp.DestsCount; i++)
|
for (int i = 0; i < texOp.DestsCount; i++)
|
||||||
|
@ -541,15 +781,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
LinkedListNode<INode> oldNode = node;
|
LinkedListNode<INode> oldNode = node;
|
||||||
|
|
||||||
// Technically, non-constant texture offsets are not allowed (according to the spec),
|
if (isGather && !isShadow)
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
config.SetUsedFeature(FeatureFlags.IntegerSampling);
|
config.SetUsedFeature(FeatureFlags.IntegerSampling);
|
||||||
|
|
||||||
|
@ -557,7 +789,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
sources.CopyTo(newSources, 0);
|
sources.CopyTo(newSources, 0);
|
||||||
|
|
||||||
Operand[] texSizes = InsertTextureSize(node, texOp, lodSources, bindlessHandle, coordsCount);
|
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
|
||||||
|
|
||||||
int destIndex = 0;
|
int destIndex = 0;
|
||||||
|
|
||||||
|
@ -576,7 +808,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
Operand intOffset = offsets[index + (hasOffsets ? compIndex * coordsCount : 0)];
|
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];
|
Operand source = sources[coordsIndex + index];
|
||||||
|
|
||||||
|
@ -603,45 +839,46 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
}
|
}
|
||||||
else
|
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++)
|
Operand intOffset = offsets[index];
|
||||||
{
|
|
||||||
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
|
|
||||||
|
|
||||||
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();
|
sources[coordsIndex + index] = coordPlusOffset;
|
||||||
|
|
||||||
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordPlusOffset, source, offset));
|
|
||||||
|
|
||||||
sources[coordsIndex + index] = coordPlusOffset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -669,22 +906,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Operand[] InsertTextureSize(
|
private static Operand[] InsertTextureLod(
|
||||||
LinkedListNode<INode> node,
|
LinkedListNode<INode> node,
|
||||||
TextureOperation texOp,
|
TextureOperation texOp,
|
||||||
Operand[] lodSources,
|
Operand[] lodSources,
|
||||||
Operand bindlessHandle,
|
Operand bindlessHandle,
|
||||||
int coordsCount)
|
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[] texSizes = new Operand[coordsCount];
|
||||||
|
|
||||||
Operand lod = Local();
|
Operand lod = Local();
|
||||||
|
@ -708,11 +936,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
if (bindlessHandle != null)
|
if (bindlessHandle != null)
|
||||||
{
|
{
|
||||||
texSizeSources = new Operand[] { bindlessHandle, Int(lod) };
|
texSizeSources = new Operand[] { bindlessHandle, GenerateF2i(node, lod) };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
texSizeSources = new Operand[] { Int(lod) };
|
texSizeSources = new Operand[] { GenerateF2i(node, lod) };
|
||||||
}
|
}
|
||||||
|
|
||||||
node.List.AddBefore(node, new TextureOperation(
|
node.List.AddBefore(node, new TextureOperation(
|
||||||
|
@ -796,6 +1024,24 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Operand GenerateI2f(LinkedListNode<INode> node, Operand value)
|
||||||
|
{
|
||||||
|
Operand res = Local();
|
||||||
|
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP32, res, value));
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Operand GenerateF2i(LinkedListNode<INode> node, Operand value)
|
||||||
|
{
|
||||||
|
Operand res = Local();
|
||||||
|
|
||||||
|
node.List.AddBefore(node, new Operation(Instruction.ConvertFP32ToS32, res, value));
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool ReplaceConstantBufferWithDrawParameters(LinkedListNode<INode> node, Operation operation)
|
private static bool ReplaceConstantBufferWithDrawParameters(LinkedListNode<INode> node, Operation operation)
|
||||||
{
|
{
|
||||||
Operand GenerateLoad(IoVariable ioVariable)
|
Operand GenerateLoad(IoVariable ioVariable)
|
||||||
|
|
|
@ -860,7 +860,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return descriptors;
|
return descriptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
public (TextureDescriptor, int) FindTextureDescriptor(AstTextureOperation texOp)
|
public TextureDescriptor FindTextureDescriptor(AstTextureOperation texOp)
|
||||||
{
|
{
|
||||||
TextureDescriptor[] descriptors = GetTextureDescriptors();
|
TextureDescriptor[] descriptors = GetTextureDescriptors();
|
||||||
|
|
||||||
|
@ -872,11 +872,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
descriptor.HandleIndex == texOp.Handle &&
|
descriptor.HandleIndex == texOp.Handle &&
|
||||||
descriptor.Format == texOp.Format)
|
descriptor.Format == texOp.Format)
|
||||||
{
|
{
|
||||||
return (descriptor, i);
|
return descriptor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (default, -1);
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int FindDescriptorIndex(TextureDescriptor[] array, AstTextureOperation texOp)
|
private static int FindDescriptorIndex(TextureDescriptor[] array, AstTextureOperation texOp)
|
||||||
|
@ -897,12 +897,30 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return -1;
|
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);
|
return FindDescriptorIndex(GetImageDescriptors(), texOp);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.Translation
|
namespace Ryujinx.Graphics.Shader.Translation
|
||||||
{
|
{
|
||||||
static class ShaderIdentifier
|
static class ShaderIdentifier
|
||||||
{
|
{
|
||||||
public static ShaderIdentification Identify(Function[] functions, ShaderConfig config)
|
public static ShaderIdentification Identify(IReadOnlyList<Function> functions, ShaderConfig config)
|
||||||
{
|
{
|
||||||
if (config.Stage == ShaderStage.Geometry &&
|
if (config.Stage == ShaderStage.Geometry &&
|
||||||
config.GpuAccessor.QueryPrimitiveTopology() == InputTopology.Triangles &&
|
config.GpuAccessor.QueryPrimitiveTopology() == InputTopology.Triangles &&
|
||||||
|
@ -20,12 +20,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
return ShaderIdentification.None;
|
return ShaderIdentification.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsLayerPassthroughGeometryShader(Function[] functions, out int layerInputAttr)
|
private static bool IsLayerPassthroughGeometryShader(IReadOnlyList<Function> functions, out int layerInputAttr)
|
||||||
{
|
{
|
||||||
bool writesLayer = false;
|
bool writesLayer = false;
|
||||||
layerInputAttr = 0;
|
layerInputAttr = 0;
|
||||||
|
|
||||||
if (functions.Length != 1)
|
if (functions.Count != 1)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||||
using Ryujinx.Graphics.Shader.Translation.Optimizations;
|
using Ryujinx.Graphics.Shader.Translation.Optimizations;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||||
|
|
||||||
|
@ -44,7 +45,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Function[] funcs = new Function[functions.Length];
|
List<Function> funcs = new List<Function>(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++)
|
for (int i = 0; i < functions.Length; i++)
|
||||||
{
|
{
|
||||||
|
@ -71,7 +79,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
Ssa.Rename(cfg.Blocks);
|
Ssa.Rename(cfg.Blocks);
|
||||||
|
|
||||||
Optimizer.RunPass(cfg.Blocks, config);
|
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);
|
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
|
||||||
|
|
|
@ -65,6 +65,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return (formatFeatureFlags & flags) == flags;
|
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)
|
public bool OptimalFormatSupports(FormatFeatureFlags flags, GAL.Format format)
|
||||||
{
|
{
|
||||||
var formatFeatureFlags = _optimalTable[(int)format];
|
var formatFeatureFlags = _optimalTable[(int)format];
|
||||||
|
|
|
@ -168,5 +168,223 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
return _table[(int)format];
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
public uint[] AttachmentSamples { get; }
|
public uint[] AttachmentSamples { get; }
|
||||||
public VkFormat[] AttachmentFormats { get; }
|
public VkFormat[] AttachmentFormats { get; }
|
||||||
public int[] AttachmentIndices { get; }
|
public int[] AttachmentIndices { get; }
|
||||||
|
public uint AttachmentIntegerFormatMask { get; }
|
||||||
|
|
||||||
public int AttachmentsCount { get; }
|
public int AttachmentsCount { get; }
|
||||||
public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[AttachmentIndices.Length - 1] : -1;
|
public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[AttachmentIndices.Length - 1] : -1;
|
||||||
|
@ -74,6 +75,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
int bindIndex = 0;
|
int bindIndex = 0;
|
||||||
|
uint attachmentIntegerFormatMask = 0;
|
||||||
|
|
||||||
foreach (ITexture color in colors)
|
foreach (ITexture color in colors)
|
||||||
{
|
{
|
||||||
|
@ -89,6 +91,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
AttachmentFormats[index] = texture.VkFormat;
|
AttachmentFormats[index] = texture.VkFormat;
|
||||||
AttachmentIndices[index] = bindIndex;
|
AttachmentIndices[index] = bindIndex;
|
||||||
|
|
||||||
|
if (texture.Info.Format.IsInteger())
|
||||||
|
{
|
||||||
|
attachmentIntegerFormatMask |= 1u << bindIndex;
|
||||||
|
}
|
||||||
|
|
||||||
width = Math.Min(width, (uint)texture.Width);
|
width = Math.Min(width, (uint)texture.Width);
|
||||||
height = Math.Min(height, (uint)texture.Height);
|
height = Math.Min(height, (uint)texture.Height);
|
||||||
layers = Math.Min(layers, (uint)texture.Layers);
|
layers = Math.Min(layers, (uint)texture.Layers);
|
||||||
|
@ -102,6 +109,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
bindIndex++;
|
bindIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
|
||||||
|
|
||||||
if (depthStencil is TextureView dsTexture && dsTexture.Valid)
|
if (depthStencil is TextureView dsTexture && dsTexture.Valid)
|
||||||
{
|
{
|
||||||
_attachments[count - 1] = dsTexture.GetImageViewForAttachment();
|
_attachments[count - 1] = dsTexture.GetImageViewForAttachment();
|
||||||
|
|
|
@ -28,6 +28,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
public readonly bool SupportsSubgroupSizeControl;
|
public readonly bool SupportsSubgroupSizeControl;
|
||||||
public readonly bool SupportsShaderInt8;
|
public readonly bool SupportsShaderInt8;
|
||||||
public readonly bool SupportsShaderStencilExport;
|
public readonly bool SupportsShaderStencilExport;
|
||||||
|
public readonly bool SupportsShaderStorageImageMultisample;
|
||||||
public readonly bool SupportsConditionalRendering;
|
public readonly bool SupportsConditionalRendering;
|
||||||
public readonly bool SupportsExtendedDynamicState;
|
public readonly bool SupportsExtendedDynamicState;
|
||||||
public readonly bool SupportsMultiView;
|
public readonly bool SupportsMultiView;
|
||||||
|
@ -42,6 +43,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
public readonly bool SupportsGeometryShader;
|
public readonly bool SupportsGeometryShader;
|
||||||
public readonly bool SupportsViewportArray2;
|
public readonly bool SupportsViewportArray2;
|
||||||
public readonly bool SupportsHostImportedMemory;
|
public readonly bool SupportsHostImportedMemory;
|
||||||
|
public readonly bool SupportsDepthClipControl;
|
||||||
public readonly uint MinSubgroupSize;
|
public readonly uint MinSubgroupSize;
|
||||||
public readonly uint MaxSubgroupSize;
|
public readonly uint MaxSubgroupSize;
|
||||||
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
|
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
|
||||||
|
@ -63,6 +65,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
bool supportsSubgroupSizeControl,
|
bool supportsSubgroupSizeControl,
|
||||||
bool supportsShaderInt8,
|
bool supportsShaderInt8,
|
||||||
bool supportsShaderStencilExport,
|
bool supportsShaderStencilExport,
|
||||||
|
bool supportsShaderStorageImageMultisample,
|
||||||
bool supportsConditionalRendering,
|
bool supportsConditionalRendering,
|
||||||
bool supportsExtendedDynamicState,
|
bool supportsExtendedDynamicState,
|
||||||
bool supportsMultiView,
|
bool supportsMultiView,
|
||||||
|
@ -77,6 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
bool supportsGeometryShader,
|
bool supportsGeometryShader,
|
||||||
bool supportsViewportArray2,
|
bool supportsViewportArray2,
|
||||||
bool supportsHostImportedMemory,
|
bool supportsHostImportedMemory,
|
||||||
|
bool supportsDepthClipControl,
|
||||||
uint minSubgroupSize,
|
uint minSubgroupSize,
|
||||||
uint maxSubgroupSize,
|
uint maxSubgroupSize,
|
||||||
ShaderStageFlags requiredSubgroupSizeStages,
|
ShaderStageFlags requiredSubgroupSizeStages,
|
||||||
|
@ -97,6 +101,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
SupportsSubgroupSizeControl = supportsSubgroupSizeControl;
|
SupportsSubgroupSizeControl = supportsSubgroupSizeControl;
|
||||||
SupportsShaderInt8 = supportsShaderInt8;
|
SupportsShaderInt8 = supportsShaderInt8;
|
||||||
SupportsShaderStencilExport = supportsShaderStencilExport;
|
SupportsShaderStencilExport = supportsShaderStencilExport;
|
||||||
|
SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;
|
||||||
SupportsConditionalRendering = supportsConditionalRendering;
|
SupportsConditionalRendering = supportsConditionalRendering;
|
||||||
SupportsExtendedDynamicState = supportsExtendedDynamicState;
|
SupportsExtendedDynamicState = supportsExtendedDynamicState;
|
||||||
SupportsMultiView = supportsMultiView;
|
SupportsMultiView = supportsMultiView;
|
||||||
|
@ -111,6 +116,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
SupportsGeometryShader = supportsGeometryShader;
|
SupportsGeometryShader = supportsGeometryShader;
|
||||||
SupportsViewportArray2 = supportsViewportArray2;
|
SupportsViewportArray2 = supportsViewportArray2;
|
||||||
SupportsHostImportedMemory = supportsHostImportedMemory;
|
SupportsHostImportedMemory = supportsHostImportedMemory;
|
||||||
|
SupportsDepthClipControl = supportsDepthClipControl;
|
||||||
MinSubgroupSize = minSubgroupSize;
|
MinSubgroupSize = minSubgroupSize;
|
||||||
MaxSubgroupSize = maxSubgroupSize;
|
MaxSubgroupSize = maxSubgroupSize;
|
||||||
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
|
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
|
||||||
|
|
|
@ -80,6 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
private PipelineColorBlendAttachmentState[] _storedBlend;
|
private PipelineColorBlendAttachmentState[] _storedBlend;
|
||||||
|
|
||||||
|
private ulong _drawCountSinceBarrier;
|
||||||
public ulong DrawCount { get; private set; }
|
public ulong DrawCount { get; private set; }
|
||||||
public bool RenderPassActive { get; private set; }
|
public bool RenderPassActive { get; private set; }
|
||||||
|
|
||||||
|
@ -133,6 +134,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public unsafe void Barrier()
|
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()
|
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
||||||
{
|
{
|
||||||
SType = StructureType.MemoryBarrier,
|
SType = StructureType.MemoryBarrier,
|
||||||
|
@ -551,7 +564,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
(uint)maxDrawCount,
|
(uint)maxDrawCount,
|
||||||
(uint)stride);
|
(uint)stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -813,8 +825,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public void SetDepthMode(DepthMode mode)
|
public void SetDepthMode(DepthMode mode)
|
||||||
{
|
{
|
||||||
// Currently this is emulated on the shader, because Vulkan had no support for changing the depth mode.
|
bool oldMode = _newState.DepthMode;
|
||||||
// In the future, we may want to use the VK_EXT_depth_clip_control extension to change it here.
|
_newState.DepthMode = mode == DepthMode.MinusOneToOne;
|
||||||
|
if (_newState.DepthMode != oldMode)
|
||||||
|
{
|
||||||
|
SignalStateChange();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDepthTest(DepthTestDescriptor depthTest)
|
public void SetDepthTest(DepthTestDescriptor depthTest)
|
||||||
|
@ -1471,6 +1487,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
var dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan();
|
var dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan();
|
||||||
FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats);
|
FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats);
|
||||||
|
_newState.Internal.AttachmentIntegerFormatMask = FramebufferParams.AttachmentIntegerFormatMask;
|
||||||
|
|
||||||
for (int i = FramebufferParams.AttachmentFormats.Length; i < dstAttachmentFormats.Length; i++)
|
for (int i = FramebufferParams.AttachmentFormats.Length; i < dstAttachmentFormats.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -294,6 +294,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
int attachmentCount = 0;
|
int attachmentCount = 0;
|
||||||
int maxColorAttachmentIndex = -1;
|
int maxColorAttachmentIndex = -1;
|
||||||
|
uint attachmentIntegerFormatMask = 0;
|
||||||
|
|
||||||
for (int i = 0; i < Constants.MaxRenderTargets; i++)
|
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]);
|
pipeline.Internal.AttachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]);
|
||||||
maxColorAttachmentIndex = 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.ColorBlendAttachmentStateCount = (uint)(maxColorAttachmentIndex + 1);
|
||||||
pipeline.VertexAttributeDescriptionsCount = (uint)Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
|
pipeline.VertexAttributeDescriptionsCount = (uint)Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
|
||||||
|
pipeline.Internal.AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
|
||||||
|
|
||||||
return pipeline;
|
return pipeline;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using Silk.NET.Vulkan;
|
using Ryujinx.Common.Memory;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
|
@ -303,11 +305,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
|
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<PipelineShaderStageCreateInfo> Stages;
|
public NativeArray<PipelineShaderStageCreateInfo> Stages;
|
||||||
public NativeArray<PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT> StageRequiredSubgroupSizes;
|
public NativeArray<PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT> StageRequiredSubgroupSizes;
|
||||||
public PipelineLayout PipelineLayout;
|
public PipelineLayout PipelineLayout;
|
||||||
public SpecData SpecializationData;
|
public SpecData SpecializationData;
|
||||||
|
|
||||||
|
private Array32<VertexInputAttributeDescription> _vertexAttributeDescriptions2;
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
Stages = new NativeArray<PipelineShaderStageCreateInfo>(Constants.MaxShaderStages);
|
Stages = new NativeArray<PipelineShaderStageCreateInfo>(Constants.MaxShaderStages);
|
||||||
|
@ -328,6 +338,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
LineWidth = 1f;
|
LineWidth = 1f;
|
||||||
SamplesCount = 1;
|
SamplesCount = 1;
|
||||||
|
DepthMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe Auto<DisposablePipeline> CreateComputePipeline(
|
public unsafe Auto<DisposablePipeline> CreateComputePipeline(
|
||||||
|
@ -400,7 +411,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
Pipeline pipelineHandle = default;
|
Pipeline pipelineHandle = default;
|
||||||
|
|
||||||
|
bool isMoltenVk = gd.IsMoltenVk;
|
||||||
|
|
||||||
|
if (isMoltenVk)
|
||||||
|
{
|
||||||
|
UpdateVertexAttributeDescriptions(gd);
|
||||||
|
}
|
||||||
|
|
||||||
fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions = &Internal.VertexAttributeDescriptions[0])
|
fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions = &Internal.VertexAttributeDescriptions[0])
|
||||||
|
fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions2 = &_vertexAttributeDescriptions2[0])
|
||||||
fixed (VertexInputBindingDescription* pVertexBindingDescriptions = &Internal.VertexBindingDescriptions[0])
|
fixed (VertexInputBindingDescription* pVertexBindingDescriptions = &Internal.VertexBindingDescriptions[0])
|
||||||
fixed (Viewport* pViewports = &Internal.Viewports[0])
|
fixed (Viewport* pViewports = &Internal.Viewports[0])
|
||||||
fixed (Rect2D* pScissors = &Internal.Scissors[0])
|
fixed (Rect2D* pScissors = &Internal.Scissors[0])
|
||||||
|
@ -410,7 +429,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
SType = StructureType.PipelineVertexInputStateCreateInfo,
|
SType = StructureType.PipelineVertexInputStateCreateInfo,
|
||||||
VertexAttributeDescriptionCount = VertexAttributeDescriptionsCount,
|
VertexAttributeDescriptionCount = VertexAttributeDescriptionsCount,
|
||||||
PVertexAttributeDescriptions = pVertexAttributeDescriptions,
|
PVertexAttributeDescriptions = isMoltenVk ? pVertexAttributeDescriptions2 : pVertexAttributeDescriptions,
|
||||||
VertexBindingDescriptionCount = VertexBindingDescriptionsCount,
|
VertexBindingDescriptionCount = VertexBindingDescriptionsCount,
|
||||||
PVertexBindingDescriptions = pVertexBindingDescriptions
|
PVertexBindingDescriptions = pVertexBindingDescriptions
|
||||||
};
|
};
|
||||||
|
@ -471,6 +490,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
PScissors = pScissors
|
PScissors = pScissors
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (gd.Capabilities.SupportsDepthClipControl)
|
||||||
|
{
|
||||||
|
var viewportDepthClipControlState = new PipelineViewportDepthClipControlCreateInfoEXT()
|
||||||
|
{
|
||||||
|
SType = StructureType.PipelineViewportDepthClipControlCreateInfoExt,
|
||||||
|
NegativeOneToOne = DepthMode
|
||||||
|
};
|
||||||
|
|
||||||
|
viewportState.PNext = &viewportDepthClipControlState;
|
||||||
|
}
|
||||||
|
|
||||||
var multisampleState = new PipelineMultisampleStateCreateInfo
|
var multisampleState = new PipelineMultisampleStateCreateInfo
|
||||||
{
|
{
|
||||||
SType = StructureType.PipelineMultisampleStateCreateInfo,
|
SType = StructureType.PipelineMultisampleStateCreateInfo,
|
||||||
|
@ -513,6 +543,27 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
MaxDepthBounds = MaxDepthBounds
|
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()
|
var colorBlendState = new PipelineColorBlendStateCreateInfo()
|
||||||
{
|
{
|
||||||
SType = StructureType.PipelineColorBlendStateCreateInfo,
|
SType = StructureType.PipelineColorBlendStateCreateInfo,
|
||||||
|
@ -590,6 +641,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
};
|
};
|
||||||
|
|
||||||
gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle).ThrowOnError();
|
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<DisposablePipeline>(new DisposablePipeline(gd.Api, device, pipelineHandle));
|
pipeline = new Auto<DisposablePipeline>(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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Stages.Dispose();
|
Stages.Dispose();
|
||||||
|
|
|
@ -35,6 +35,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
public Array16<Rect2D> Scissors;
|
public Array16<Rect2D> Scissors;
|
||||||
public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState;
|
public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState;
|
||||||
public Array9<Format> AttachmentFormats;
|
public Array9<Format> AttachmentFormats;
|
||||||
|
public uint AttachmentIntegerFormatMask;
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
|
|
|
@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
|
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;
|
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;
|
var usage = DefaultUsageFlags;
|
||||||
|
|
||||||
|
@ -306,7 +306,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
usage |= ImageUsageFlags.ColorAttachmentBit;
|
usage |= ImageUsageFlags.ColorAttachmentBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (format.IsImageCompatible())
|
if (format.IsImageCompatible() && (supportsMsStorage || !target.IsMultisample()))
|
||||||
{
|
{
|
||||||
usage |= ImageUsageFlags.StorageBit;
|
usage |= ImageUsageFlags.StorageBit;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
gd.Textures.Add(this);
|
gd.Textures.Add(this);
|
||||||
|
|
||||||
var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
|
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 levels = (uint)info.Levels;
|
||||||
var layers = (uint)info.GetLayers();
|
var layers = (uint)info.GetLayers();
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
"VK_EXT_subgroup_size_control",
|
"VK_EXT_subgroup_size_control",
|
||||||
"VK_NV_geometry_shader_passthrough",
|
"VK_NV_geometry_shader_passthrough",
|
||||||
"VK_NV_viewport_array2",
|
"VK_NV_viewport_array2",
|
||||||
|
"VK_EXT_depth_clip_control",
|
||||||
"VK_KHR_portability_subset" // As per spec, we should enable this if present.
|
"VK_KHR_portability_subset" // As per spec, we should enable this if present.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -345,6 +346,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
features2.PNext = &supportedFeaturesRobustness2;
|
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);
|
api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2);
|
||||||
|
|
||||||
var supportedFeatures = features2.Features;
|
var supportedFeatures = features2.Features;
|
||||||
|
@ -507,6 +519,21 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
pExtendedFeatures = &featuresCustomBorderColor;
|
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();
|
var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray();
|
||||||
|
|
||||||
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
|
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
|
||||||
|
|
|
@ -216,6 +216,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt
|
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl = new PhysicalDeviceDepthClipControlFeaturesEXT()
|
||||||
|
{
|
||||||
|
SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt
|
||||||
|
};
|
||||||
|
|
||||||
PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new PhysicalDevicePortabilitySubsetFeaturesKHR()
|
PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new PhysicalDevicePortabilitySubsetFeaturesKHR()
|
||||||
{
|
{
|
||||||
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr
|
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr
|
||||||
|
@ -244,6 +249,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
features2.PNext = &featuresCustomBorderColor;
|
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");
|
bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset");
|
||||||
|
|
||||||
if (usePortability)
|
if (usePortability)
|
||||||
|
@ -295,6 +308,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
supportsSubgroupSizeControl,
|
supportsSubgroupSizeControl,
|
||||||
featuresShaderInt8.ShaderInt8,
|
featuresShaderInt8.ShaderInt8,
|
||||||
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
|
||||||
|
features2.Features.ShaderStorageImageMultisample,
|
||||||
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
|
||||||
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
|
||||||
features2.Features.MultiViewport,
|
features2.Features.MultiViewport,
|
||||||
|
@ -309,6 +323,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_physicalDevice.PhysicalDeviceFeatures.GeometryShader,
|
_physicalDevice.PhysicalDeviceFeatures.GeometryShader,
|
||||||
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"),
|
||||||
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName),
|
||||||
|
supportsDepthClipControl && featuresDepthClipControl.DepthClipControl,
|
||||||
propertiesSubgroupSizeControl.MinSubgroupSize,
|
propertiesSubgroupSizeControl.MinSubgroupSize,
|
||||||
propertiesSubgroupSizeControl.MaxSubgroupSize,
|
propertiesSubgroupSizeControl.MaxSubgroupSize,
|
||||||
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
|
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
|
||||||
|
@ -584,6 +599,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
supportsViewportMask: Capabilities.SupportsViewportArray2,
|
supportsViewportMask: Capabilities.SupportsViewportArray2,
|
||||||
supportsViewportSwizzle: false,
|
supportsViewportSwizzle: false,
|
||||||
supportsIndirectParameters: true,
|
supportsIndirectParameters: true,
|
||||||
|
supportsDepthClipControl: Capabilities.SupportsDepthClipControl,
|
||||||
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
|
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
|
||||||
maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage,
|
maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage,
|
||||||
maximumTexturesPerStage: Constants.MaxTexturesPerStage,
|
maximumTexturesPerStage: Constants.MaxTexturesPerStage,
|
||||||
|
|
|
@ -167,12 +167,12 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
if (StrEquals(RomfsDir, modDir.Name))
|
if (StrEquals(RomfsDir, modDir.Name))
|
||||||
{
|
{
|
||||||
mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleId} RomFs>", modDir));
|
mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>(dir.Name, modDir));
|
||||||
types.Append('R');
|
types.Append('R');
|
||||||
}
|
}
|
||||||
else if (StrEquals(ExefsDir, modDir.Name))
|
else if (StrEquals(ExefsDir, modDir.Name))
|
||||||
{
|
{
|
||||||
mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleId} ExeFs>", modDir));
|
mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>(dir.Name, modDir));
|
||||||
types.Append('E');
|
types.Append('E');
|
||||||
}
|
}
|
||||||
else if (StrEquals(CheatDir, modDir.Name))
|
else if (StrEquals(CheatDir, modDir.Name))
|
||||||
|
|
|
@ -1024,6 +1024,8 @@ namespace Ryujinx.Ui
|
||||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
|
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
|
||||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appMetadata.LastPlayed = DateTime.UtcNow;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 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
|
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")
|
_ => System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
22
src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs
generated
22
src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs
generated
|
@ -48,6 +48,8 @@ namespace Ryujinx.Ui.Windows
|
||||||
private Label _patreonNamesLabel;
|
private Label _patreonNamesLabel;
|
||||||
private ScrolledWindow _patreonNamesScrolled;
|
private ScrolledWindow _patreonNamesScrolled;
|
||||||
private TextView _patreonNamesText;
|
private TextView _patreonNamesText;
|
||||||
|
private EventBox _changelogEventBox;
|
||||||
|
private Label _changelogLinkLabel;
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
|
@ -148,6 +150,23 @@ namespace Ryujinx.Ui.Windows
|
||||||
Margin = 5
|
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
|
// _disclaimerLabel
|
||||||
//
|
//
|
||||||
|
@ -464,8 +483,11 @@ namespace Ryujinx.Ui.Windows
|
||||||
_socialBox.Add(_discordEventBox);
|
_socialBox.Add(_discordEventBox);
|
||||||
_socialBox.Add(_twitterEventBox);
|
_socialBox.Add(_twitterEventBox);
|
||||||
|
|
||||||
|
_changelogEventBox.Add(_changelogLinkLabel);
|
||||||
|
|
||||||
_leftBox.Add(_logoBox);
|
_leftBox.Add(_logoBox);
|
||||||
_leftBox.Add(_versionLabel);
|
_leftBox.Add(_versionLabel);
|
||||||
|
_leftBox.Add(_changelogEventBox);
|
||||||
_leftBox.Add(_disclaimerLabel);
|
_leftBox.Add(_disclaimerLabel);
|
||||||
_leftBox.Add(_amiiboApiLink);
|
_leftBox.Add(_amiiboApiLink);
|
||||||
_leftBox.Add(_socialBox);
|
_leftBox.Add(_socialBox);
|
||||||
|
|
|
@ -76,5 +76,10 @@ namespace Ryujinx.Ui.Windows
|
||||||
{
|
{
|
||||||
OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue