Compare commits

..

42 Commits

Author SHA1 Message Date
8a352df3c6 Allow BGRA images on Vulkan (#5203) 2023-06-03 03:43:00 +00:00
c545c59851 ava: Fix exit dialog while guest is running. (#5207)
* ava: Fix exit dialog while guest is running.

There is currently an issue while a game runs, the content dialog creation method check if `IsGameRunning` is true to show the popup.
But the condition here is wrong (`window` is null) so it throw a NullException silently in `Dispatcher.UIThread`.
This is now fixed by using the right casting.

* improve condition

* Fix spacing
2023-06-03 03:37:00 +00:00
96ea4e8c8e nuget: bump Microsoft.NET.Test.Sdk from 17.6.0 to 17.6.1 (#5192)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.0 to 17.6.1.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.6.0...v17.6.1)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-01 21:03:00 +02:00
b8f48bcf64 UI: Fix empty homebrew icon (#5189)
* UI: Fix empty homebrew icon

We currently don't check the icon size when we read it from the homebrew data. That could cause issues at UI side since the buffer isn't null but empty. Extra check have been added UI side too.
(I cleaned up some files during my research too)

Fixes #5188

* Remove additional check

* Remove unused using
2023-06-01 18:24:00 +02:00
6966211e07 Give Library header DockPanel explicit height (#5160) 2023-06-01 17:40:44 +02:00
57524a4c8a Add issue template for missing shader instructions (#5048)
* Add issue template for missing shader instructions

* fixup! Add issue template for missing shader instructions

* Update .github/ISSUE_TEMPLATE/missing_shader_instruction.yml

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-06-01 17:32:38 +02:00
f4539c49d8 [Logger] Add print with stacktrace method (#5129)
* Add print with stacktrace method

* Adjust logging namespaces

* Add static keyword to DynamicObjectFormatter
2023-06-01 13:47:53 +00:00
12c62fdbc2 nuget: bump DynamicData from 7.13.8 to 7.14.2 (#5148)
Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 7.13.8 to 7.14.2.
- [Release notes](https://github.com/reactiveui/DynamicData/releases)
- [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md)
- [Commits](https://github.com/reactiveui/DynamicData/compare/7.13.8...7.14.2)

---
updated-dependencies:
- dependency-name: DynamicData
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-01 13:35:04 +00:00
e3c6be5e29 Only run one workflow for a PR at a time (#5137) 2023-06-01 09:42:49 +02:00
4741a05df9 Vulkan: Include DepthMode in ProgramPipelineState (#5185) 2023-06-01 09:05:39 +02:00
c6676007bf GPU: Dispose Renderer after running deferred actions (#5144)
* GAL: Dispose Renderer after running deferred actions

Deferred actions from disposing physical memory instances always dispose the resources in their caches. The renderer can't be disposed before these resources get disposed, otherwise the dispose actions will not actually run, and the ThreadedRenderer may get stuck trying to enqueue too many commands when there is nothing consuming them.

This should fix most instances of the emulator freezing on close.

* Wait for main render commands to finish, but keep RenderThread alive til dispose

* Address some feedback.

* No parameterize needed

* Set thread name as part of constructor

* Port to Ava and SDL2
2023-05-31 21:43:20 +00:00
92b0b7d753 Avalonia UI: Fix letter "x" in Ryujinx logo being cut off (#5176)
Also make the pronunciation center-aligned
2023-05-31 21:03:11 +00:00
232237bf28 Skip draws with zero vertex count (#5149) 2023-05-31 17:51:11 -03:00
c27e453fd3 Share ResourceManager vertex vertex A and B shaders (#5181) 2023-05-31 17:17:50 -03:00
0e037d0213 macOS Headless Fixes (#5167)
* Default hypervisor to disabled

* Include MVK on macOS

* Properly sign headless builds on macOS

* Force Vulkan on macOS

* Suggestions
2023-05-31 09:08:50 +02:00
0dca1fbe12 Add Context Menu Option to Run Application (#5154) 2023-05-30 19:51:03 +01:00
35d91a0e58 Linux: Automatically increase vm.max_map_count if it's too low (#4702)
* memory: Check results of pinvoke calls

* Increase vm.max_map_count when running Ryujinx

* Add SupportedOSPlatform attribute for WindowsApiException

* Revert increasing vm.max_map_count via script

* Add LinuxHelper to detect and increase vm.max_map_count

With GUI dialogs, this should be a bit more user-friendly.

* Supply arguments as a list to RunPkExec

* Add error logging in case RunPkExec() fails

* Prevent Gtk from crashing
2023-05-30 01:48:37 +02:00
a73a5d7e85 nuget: bump Microsoft.NET.Test.Sdk from 17.5.0 to 17.6.0 (#4986)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.5.0 to 17.6.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.5.0...v17.6.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-29 01:14:07 +02:00
832a5e8852 Make sure blend is disabled if render target has integer format (#5122)
* Make sure blend is disabled if render target has integer format

* Change approach to avoid permanently mutating state
2023-05-29 00:38:04 +02:00
96d1f0da2d Workaround for MoltenVK barrier issues (#5118) 2023-05-29 00:24:35 +02:00
597388ecda Fix incorrect vertex attribute format change (#5112)
* Fix incorrect vertex attribute format change

* Only change vertex format if the host supports the new format
2023-05-29 00:17:07 +02:00
1cf6d7b7bb Fix #5108: Allow surround sound for SDL2 in more scenarios (#5131) 2023-05-29 00:07:27 +02:00
7bc9d0cdad Linux: Use gamemode if it is available when using Ryujinx.sh. (#4938)
* Linux: Detect if gamemode is installed and start it when launching Ryujinx.

When using the Ryujinx.sh script to start the emulator check if gamemoderun exists and use it if it does.

Gamemode mode on Linux changes some system settings to make performance during gaming more consistent mainly by changing the CPU governor to performance.

https://github.com/FeralInteractive/gamemode

* Removed if statement.

* Fix due to wrong assumption about the output of which.

Checks if the which output contains a no match response, otherwise use gamemoderun.

Using a case statement because it makes substring matching possible in sh and also it turns out that adding an empty string after env throws an error because env attempts to parse it as a paramater.

* Missed a couple semicolons.

* Different approach for checking if gamemode is available.

Should hopefully work across all implementations of which.

* Remove unneeded which command.

* Change code to keep launch command to a single line.
2023-05-28 23:54:22 +02:00
dc0dbc50ab Add support for VK_EXT_depth_clip_control. (#5027)
* Add support for VK_EXT_depth_clip_control.

* Code review feedback

Minor formatting

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Check .DepthClipControl to make sure the host actually supports the feature.

* Review feedback: remove Vulkan platform switch, relying on QueryHostSupportsDepthClipControl to drive the behaviour - OpenGL returns true, and any future platforms that don't support the [-1, 1] depth mode can return false for the transformation.

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-05-28 23:31:56 +02:00
994f4dc77d chore: Update Avalonia to 0.10.21 (#5124) 2023-05-28 23:25:55 +02:00
c9e297b74c About window: Add changelog link under ver. number (#5095) 2023-05-28 23:13:40 +02:00
dd514a115c Update LastPlayed date on emulation end. (#5056) 2023-05-28 23:03:27 +02:00
7e0b4bd538 Improve macOS updater (#5064)
* Fix macOS Updater (once again)

* Also fix my brain's issues

* Move set -e that lsof doesn't trigger exit 1

* Resolve yesterdays brain malfunction 2

* Revert "Move set -e that lsof doesn't trigger exit 1"

This reverts commit 589a630610.

* Also check if PID exists

* Remove lsof and instead check for running processes

* Remove empty lines

* Increase max iterations

* Address feedback

* Remove obsolete check for child processes

* Update comments

* Update comments

* I swear this is the last commit

* lsof + ps check
2023-05-28 22:54:30 +02:00
378080eb87 Added Custom Path case when saving screenshots (#5086) 2023-05-28 22:44:46 +02:00
e8f5e97fa4 actions: revert timeout-minutes changes for PR workflow
Varibales aren't exposed to PRs...
2023-05-28 11:34:57 +02:00
f3873620a3 actions: Workaround YAML limitation for timeout-minutes
Because Github Actions wants an int, we use fromJSON to hack around
this.
2023-05-28 08:10:43 +02:00
986ac9ff83 Use variables to configure job timeouts (#5123) 2023-05-28 08:02:30 +02:00
42b9c1e8fe Ryujinx.Ava: fixes for random hangs on exit (#4827)
* Attempt at fixing hang on exit by ending the WindowNotificationManager notification loop, so that the Thread running it can exit.

* explicitly apply the NotificationManager template to allow the notification loop to begin

* NotificationHelper - remove explicity call to ApplyTemplate(). Change to ManualResetEventSlim so we can cancel the Wait on it.

* add a timeout to AudioRenderSystem.Stop()'s waiting for the termination signal, log a warning if this timeout occurs, and continue execution

* NotifiationHelper - cancel first, the CompleteAdding()

* Remove AudioRenderSystem._terminationEvent, redundant

* NotificationHelper - use host.Closing event to trigger cancellation instead of _notifationManager.DetachedFromLogicalTree

* Change NotificationHelper to use an explicit Thread for background work.  Wait on the cancellationToken's WaitHandle so the Thread doesn't have to deal with async. Wrap foreach in try/catch (OperationCanceledException) to swallow the escaping exception from the GetConsumingEnumerable().

* adjust formatting of AsyncWorkQueue constructor to use object initializers consistently

* use AsyncWorkQueue to do everything I added in SetNotificationManager()

* Revert "use AsyncWorkQueue to do everything I added in SetNotificationManager()"

This reverts commit f0e78366b8776ec8e2fef8ab023c0db1833155d3.

* use AsyncWorkQueue to handle the Thread-related changes previously made to NotificationHelper.SetNotificationHelper(). Wrap it in Lazy<T> and force instantiation in the TemplateApplied event handler to accomodate for the fact that AsyncWorkQueue starts immediately, and the notification dispatch loop was being delayed by _templateAppliedEvent.

* impl changes suggested by AcK77

* impl changes suggested by AcK77 (more)
2023-05-26 23:57:43 +02:00
3b375525fb Force reciprocal operation with value biased by constant to be precise on macOS (#5110)
* Force operations to be precise in some cases on SPIR-V

* Make it a bit more strict, add comments

* Shader cache version bump
2023-05-26 15:19:37 -03:00
e6658c133c Fix resolution scaling of image operation coordinates (#5102)
* Fix resolution scaling of image operation coordinates

* Shader cache version bump
2023-05-25 23:42:49 -03:00
5b42a4d2c4 Fix mod names (#5088) 2023-05-25 23:41:03 +02:00
8f0c89ffd6 Generate scaling helper functions on IR (#4714)
* Generate scaling helper functions on IR

* Delete unused code

* Split RewriteTextureSample and move gather bias add to an earlier pass

* Remove using

* Shader cache version bump
2023-05-25 17:46:58 -03:00
2c9715acf6 Truncate vertex attribute format if it exceeds stride on MoltenVK (#5094)
* Truncate vertex attribute format if it exceeds stride on MoltenVK

* Fix BGR format

* Move vertex attribute check to pipeline creation to avoid costs

* No need for this to be public
2023-05-25 17:03:51 -03:00
274af65f69 Update release.yml (#5058) 2023-05-25 16:17:37 +02:00
4ca78eded5 Vulkan: Do not set storage flag for multisample textures if not supported (#5060) 2023-05-23 10:41:37 +02:00
6cb6b15612 Implement p2rc, p2ri, p2rr and r2p.cc shaders (#5031)
* implement P2rC, P2rI, P2rR shaders

* implement R2p.CC shader

* bump CodeGenVersion

* address feedback
2023-05-22 17:32:15 -03:00
2725e40838 Revert "Bump MVK Version (#5057)" (#5061)
This reverts commit c2e4c8f98e.
2023-05-22 17:12:11 -03:00
113 changed files with 1777 additions and 874 deletions

View File

@ -0,0 +1,19 @@
name: Missing Shader Instruction
description: Shader Instruction is missing in Ryujinx.
title: "[GPU]"
labels: [gpu, not-implemented]
body:
- type: textarea
id: instruction
attributes:
label: Shader instruction
description: What shader instruction is missing?
validations:
required: true
- type: textarea
id: required
attributes:
label: Required by
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
validations:
required: true

View File

@ -18,6 +18,10 @@ on:
- '*.yml' - '*.yml'
- 'README.md' - 'README.md'
concurrency:
group: pr-checks-${{ github.event.number }}
cancel-in-progress: true
env: env:
POWERSHELL_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
@ -27,7 +31,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 +114,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 ]

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -3,17 +3,17 @@
<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" />
<PackageVersion Include="Concentus" Version="1.1.7" /> <PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" /> <PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
<PackageVersion Include="DynamicData" Version="7.13.8" /> <PackageVersion Include="DynamicData" Version="7.14.2" />
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" /> <PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" /> <PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" /> <PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
@ -21,7 +21,7 @@
<PackageVersion Include="LibHac" Version="0.18.0" /> <PackageVersion Include="LibHac" Version="0.18.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.1" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" /> <PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" /> <PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NUnit" Version="3.13.3" /> <PackageVersion Include="NUnit" Version="3.13.3" />
@ -32,7 +32,7 @@
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.7" /> <PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.7" />
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" /> <PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.3" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" /> <PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.3-build25" /> <PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.3-build25" />
<PackageVersion Include="shaderc.net" Version="0.1.0" /> <PackageVersion Include="shaderc.net" Version="0.1.0" />

View File

@ -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" "$@"

View File

@ -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"

View File

@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Backends.SDL2
} }
else else
{ {
_supportSurroundConfiguration = spec.channels == 6; _supportSurroundConfiguration = spec.channels >= 6;
} }
} }

View File

@ -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)

View File

@ -92,6 +92,8 @@ namespace Ryujinx.Ava
private bool _isActive; private bool _isActive;
private bool _renderingStarted; private bool _renderingStarted;
private ManualResetEvent _gpuDoneEvent;
private IRenderer _renderer; private IRenderer _renderer;
private readonly Thread _renderingThread; private readonly Thread _renderingThread;
private readonly CancellationTokenSource _gpuCancellationTokenSource; private readonly CancellationTokenSource _gpuCancellationTokenSource;
@ -183,6 +185,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState; ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
_gpuCancellationTokenSource = new CancellationTokenSource(); _gpuCancellationTokenSource = new CancellationTokenSource();
_gpuDoneEvent = new ManualResetEvent(false);
} }
private void TopLevel_PointerEnterOrMoved(object sender, PointerEventArgs e) private void TopLevel_PointerEnterOrMoved(object sender, PointerEventArgs e)
@ -270,7 +273,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")
}; };
@ -423,10 +426,10 @@ namespace Ryujinx.Ava
_isActive = false; _isActive = false;
if (_renderingThread.IsAlive) // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
{ // We only need to wait for all commands submitted during the main gpu loop to be processed.
_renderingThread.Join(); _gpuDoneEvent.WaitOne();
} _gpuDoneEvent.Dispose();
DisplaySleep.Restore(); DisplaySleep.Restore();
@ -917,6 +920,14 @@ namespace Ryujinx.Ava
UpdateStatus(); UpdateStatus();
} }
} }
// Make sure all commands in the run loop are fully executed before leaving the loop.
if (Device.Gpu.Renderer is ThreadedRenderer threaded)
{
threaded.FlushThreadedCommands();
}
_gpuDoneEvent.Set();
}); });
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null); (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);

View File

@ -74,6 +74,13 @@
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)", "GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
"StatusBarGamesLoaded": "{0}/{1} Games Loaded", "StatusBarGamesLoaded": "{0}/{1} Games Loaded",
"StatusBarSystemVersion": "System Version: {0}", "StatusBarSystemVersion": "System Version: {0}",
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
"LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}",
"LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.",
"LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart",
"LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently",
"LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.",
"LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.",
"Settings": "Settings", "Settings": "Settings",
"SettingsTabGeneral": "User Interface", "SettingsTabGeneral": "User Interface",
"SettingsTabGeneralGeneral": "General", "SettingsTabGeneralGeneral": "General",
@ -282,6 +289,7 @@
"ControllerSettingsSaveProfileToolTip": "Save Profile", "ControllerSettingsSaveProfileToolTip": "Save Profile",
"MenuBarFileToolsTakeScreenshot": "Take Screenshot", "MenuBarFileToolsTakeScreenshot": "Take Screenshot",
"MenuBarFileToolsHideUi": "Hide UI", "MenuBarFileToolsHideUi": "Hide UI",
"GameListContextMenuRunApplication": "Run Application",
"GameListContextMenuToggleFavorite": "Toggle Favorite", "GameListContextMenuToggleFavorite": "Toggle Favorite",
"GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game", "GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game",
"SettingsTabGeneralTheme": "Theme", "SettingsTabGeneralTheme": "Theme",
@ -620,7 +628,7 @@
"Search": "Search", "Search": "Search",
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts", "UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
"Recover": "Recover", "Recover": "Recover",
"UserProfilesRecoverHeading" : "Saves were found for the following accounts", "UserProfilesRecoverHeading": "Saves were found for the following accounts",
"UserProfilesRecoverEmptyList": "No profiles to recover", "UserProfilesRecoverEmptyList": "No profiles to recover",
"GraphicsAATooltip": "Applies anti-aliasing to the game render", "GraphicsAATooltip": "Applies anti-aliasing to the game render",
"GraphicsAALabel": "Anti-Aliasing:", "GraphicsAALabel": "Anti-Aliasing:",
@ -632,10 +640,12 @@
"SmaaMedium": "SMAA Medium", "SmaaMedium": "SMAA Medium",
"SmaaHigh": "SMAA High", "SmaaHigh": "SMAA High",
"SmaaUltra": "SMAA Ultra", "SmaaUltra": "SMAA Ultra",
"UserEditorTitle" : "Edit User", "UserEditorTitle": "Edit User",
"UserEditorTitleCreate" : "Create User", "UserEditorTitleCreate": "Create User",
"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."
} }

View File

@ -18,6 +18,7 @@
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''"> <PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
<PublishSingleFile>true</PublishSingleFile> <PublishSingleFile>true</PublishSingleFile>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<PublishTrimmed>true</PublishTrimmed> <PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode> <TrimMode>partial</TrimMode>
</PropertyGroup> </PropertyGroup>
@ -147,4 +148,4 @@
<ItemGroup> <ItemGroup>
<AdditionalFiles Include="Assets\Locales\en_US.json" /> <AdditionalFiles Include="Assets\Locales\en_US.json" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -3,6 +3,9 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"> xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale">
<MenuItem
Click="RunApplication_Click"
Header="{locale:Locale GameListContextMenuRunApplication}" />
<MenuItem <MenuItem
Click="ToggleFavorite_Click" Click="ToggleFavorite_Click"
Header="{locale:Locale GameListContextMenuToggleFavorite}" Header="{locale:Locale GameListContextMenuToggleFavorite}"

View File

@ -323,5 +323,15 @@ namespace Ryujinx.Ava.UI.Controls
await ApplicationHelper.ExtractSection(NcaSectionType.Logo, viewModel.SelectedApplication.Path, viewModel.SelectedApplication.TitleName); await ApplicationHelper.ExtractSection(NcaSectionType.Logo, viewModel.SelectedApplication.Path, viewModel.SelectedApplication.TitleName);
} }
} }
public void RunApplication_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
viewModel.LoadApplication(viewModel.SelectedApplication.Path);
}
}
} }
} }

View File

@ -21,6 +21,7 @@ namespace Ryujinx.Ava.UI.Helpers
if (value is byte[] buffer && targetType == typeof(IImage)) if (value is byte[] buffer && targetType == typeof(IImage))
{ {
MemoryStream mem = new(buffer); MemoryStream mem = new(buffer);
return new Bitmap(mem); return new Bitmap(mem);
} }

View File

@ -6,7 +6,6 @@ using Avalonia.Threading;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using System; using System;
@ -19,7 +18,7 @@ namespace Ryujinx.Ava.UI.Helpers
{ {
private static bool _isChoiceDialogOpen; private static bool _isChoiceDialogOpen;
public async static Task<UserResult> ShowContentDialog( private async static Task<UserResult> ShowContentDialog(
string title, string title,
object content, object content,
string primaryButton, string primaryButton,
@ -67,7 +66,7 @@ namespace Ryujinx.Ava.UI.Helpers
return result; return result;
} }
private async static Task<UserResult> ShowTextDialog( public async static Task<UserResult> ShowTextDialog(
string title, string title,
string primaryText, string primaryText,
string secondaryText, string secondaryText,
@ -319,7 +318,7 @@ namespace Ryujinx.Ava.UI.Helpers
Window parent = GetMainWindow(); Window parent = GetMainWindow();
if (parent != null && parent.IsActive && parent is MainWindow window && window.ViewModel.IsGameRunning) if (parent != null && parent.IsActive && (parent as MainWindow).ViewModel.IsGameRunning)
{ {
contentDialogOverlayWindow = new() contentDialogOverlayWindow = new()
{ {

View File

@ -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)

View File

@ -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;
}); });
} }

View File

@ -9,9 +9,7 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common; using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration; using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;

View File

@ -16,6 +16,7 @@
</Design.DataContext> </Design.DataContext>
<DockPanel <DockPanel
Margin="0,0,0,5" Margin="0,0,0,5"
Height="35"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch">
<Button <Button
Width="40" Width="40"

View File

@ -58,11 +58,20 @@
JustifyContent="SpaceAround" JustifyContent="SpaceAround"
RowSpacing="2"> RowSpacing="2">
<TextBlock <TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="28" FontSize="28"
FontWeight="Bold" FontWeight="Bold"
Text="Ryujinx" Text="Ryujinx"
TextAlignment="Left" /> TextAlignment="Center"
<TextBlock Text="(REE-YOU-JINX)" TextAlignment="Left" /> Width="100" />
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="11"
Text="(REE-YOU-JINX)"
TextAlignment="Center"
Width="100" />
</flex:FlexPanel> </flex:FlexPanel>
</Grid> </Grid>
<TextBlock <TextBlock
@ -72,6 +81,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"

View File

@ -23,6 +23,7 @@ using Ryujinx.Ui.Common.Helper;
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Runtime.Versioning;
using System.Threading.Tasks; using System.Threading.Tasks;
using InputManager = Ryujinx.Input.HLE.InputManager; using InputManager = Ryujinx.Input.HLE.InputManager;
@ -258,7 +259,64 @@ namespace Ryujinx.Ava.UI.Windows
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this); ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this);
} }
protected void CheckLaunchState() [SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountWarning()
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary,
LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount);
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextPrimary],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary]
);
}
[SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountDialog()
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary,
LinuxHelper.RecommendedVmMaxMapCount);
UserResult response = await ContentDialogHelper.ShowTextDialog(
$"Ryujinx - {LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTitle]}",
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextSecondary],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonUntilRestart],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonPersistent],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
(int)Symbol.Help
);
int rc;
switch (response)
{
case UserResult.Ok:
rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}");
if (rc == 0)
{
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart.");
}
else
{
Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}");
}
break;
case UserResult.No:
rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}");
if (rc == 0)
{
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}");
}
else
{
Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}");
}
break;
}
}
private void CheckLaunchState()
{ {
if (ShowKeyErrorOnLoad) if (ShowKeyErrorOnLoad)
{ {
@ -268,6 +326,20 @@ namespace Ryujinx.Ava.UI.Windows
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, this)); UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, this));
} }
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{
Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})");
if (LinuxHelper.PkExecPath is not null)
{
Dispatcher.UIThread.Post(ShowVmMaxMapCountDialog);
}
else
{
Dispatcher.UIThread.Post(ShowVmMaxMapCountWarning);
}
}
if (_deferLoad) if (_deferLoad)
{ {
_deferLoad = false; _deferLoad = false;
@ -447,14 +519,14 @@ namespace Ryujinx.Ava.UI.Windows
private void ConfirmExit() private void ConfirmExit()
{ {
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog(); ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
if (ViewModel.IsClosing) if (ViewModel.IsClosing)
{ {
Close(); Close();
} }
}); });
} }
public async void LoadApplications() public async void LoadApplications()

View File

@ -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();
} }

View File

@ -1,6 +1,7 @@
using System.Text; using System.Diagnostics;
using System.Text;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging.Formatters
{ {
internal class DefaultLogFormatter : ILogFormatter internal class DefaultLogFormatter : ILogFormatter
{ {
@ -27,6 +28,14 @@ namespace Ryujinx.Common.Logging
if (args.Data is not null) if (args.Data is not null)
{ {
if (args.Data is StackTrace trace)
{
sb.Append('\n');
sb.Append(trace);
return sb.ToString();
}
sb.Append(' '); sb.Append(' ');
DynamicObjectFormatter.Format(sb, args.Data); DynamicObjectFormatter.Format(sb, args.Data);
} }
@ -39,4 +48,4 @@ namespace Ryujinx.Common.Logging
} }
} }
} }
} }

View File

@ -3,9 +3,9 @@ using System;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging.Formatters
{ {
internal class DynamicObjectFormatter internal static class DynamicObjectFormatter
{ {
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>(); private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
@ -17,7 +17,7 @@ namespace Ryujinx.Common.Logging
} }
StringBuilder sb = StringBuilderPool.Allocate(); StringBuilder sb = StringBuilderPool.Allocate();
try try
{ {
Format(sb, dynamicObject); Format(sb, dynamicObject);

View File

@ -1,7 +1,7 @@
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging.Formatters
{ {
interface ILogFormatter interface ILogFormatter
{ {
string Format(LogEventArgs args); string Format(LogEventArgs args);
} }
} }

View File

@ -1,4 +1,5 @@
using System; using Ryujinx.Common.Logging.Formatters;
using System;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging

View File

@ -1,3 +1,4 @@
using Ryujinx.Common.Logging.Targets;
using Ryujinx.Common.SystemInterop; using Ryujinx.Common.SystemInterop;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -55,6 +56,16 @@ namespace Ryujinx.Common.Logging
} }
} }
[StackTraceHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrintStack(LogClass logClass, string message, [CallerMemberName] string caller = "")
{
if (m_EnabledClasses[(int)logClass])
{
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message), new StackTrace(true)));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrintStub(LogClass logClass, string message = "", [CallerMemberName] string caller = "") public void PrintStub(LogClass logClass, string message = "", [CallerMemberName] string caller = "")
{ {
@ -122,7 +133,7 @@ namespace Ryujinx.Common.Logging
AsyncLogTargetOverflowAction.Discard)); AsyncLogTargetOverflowAction.Discard));
Notice = new Log(LogLevel.Notice); Notice = new Log(LogLevel.Notice);
// Enable important log levels before configuration is loaded // Enable important log levels before configuration is loaded
Error = new Log(LogLevel.Error); Error = new Log(LogLevel.Error);
Warning = new Log(LogLevel.Warning); Warning = new Log(LogLevel.Warning);
@ -221,4 +232,4 @@ namespace Ryujinx.Common.Logging
m_EnabledClasses[(int)logClass] = enabled; m_EnabledClasses[(int)logClass] = enabled;
} }
} }
} }

View File

@ -2,7 +2,7 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading; using System.Threading;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging.Targets
{ {
public enum AsyncLogTargetOverflowAction public enum AsyncLogTargetOverflowAction
{ {

View File

@ -1,6 +1,7 @@
using System; using Ryujinx.Common.Logging.Formatters;
using System;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging.Targets
{ {
public class ConsoleLogTarget : ILogTarget public class ConsoleLogTarget : ILogTarget
{ {
@ -38,4 +39,4 @@ namespace Ryujinx.Common.Logging
Console.ResetColor(); Console.ResetColor();
} }
} }
} }

View File

@ -1,8 +1,9 @@
using System; using Ryujinx.Common.Logging.Formatters;
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging.Targets
{ {
public class FileLogTarget : ILogTarget public class FileLogTarget : ILogTarget
{ {

View File

@ -1,6 +1,6 @@
using System; using System;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging.Targets
{ {
public interface ILogTarget : IDisposable public interface ILogTarget : IDisposable
{ {

View File

@ -1,7 +1,7 @@
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using System.IO; using System.IO;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging.Targets
{ {
public class JsonLogTarget : ILogTarget public class JsonLogTarget : ILogTarget
{ {

View File

@ -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;

View File

@ -383,6 +383,7 @@ namespace Ryujinx.Graphics.GAL
case Format.R10G10B10A2Unorm: case Format.R10G10B10A2Unorm:
case Format.R10G10B10A2Uint: case Format.R10G10B10A2Uint:
case Format.R11G11B10Float: case Format.R11G11B10Float:
case Format.B8G8R8A8Unorm:
return true; return true;
} }

View File

@ -1,5 +1,6 @@
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using System; using System;
using System.Threading;
namespace Ryujinx.Graphics.GAL namespace Ryujinx.Graphics.GAL
{ {
@ -52,7 +53,7 @@ namespace Ryujinx.Graphics.GAL
void ResetCounter(CounterType type); void ResetCounter(CounterType type);
void RunLoop(Action gpuLoop) void RunLoop(ThreadStart gpuLoop)
{ {
gpuLoop(); gpuLoop();
} }

View File

@ -30,7 +30,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
private IRenderer _baseRenderer; private IRenderer _baseRenderer;
private Thread _gpuThread; private Thread _gpuThread;
private Thread _backendThread; private Thread _backendThread;
private bool _disposed;
private bool _running; private bool _running;
private AutoResetEvent _frameComplete = new AutoResetEvent(true); private AutoResetEvent _frameComplete = new AutoResetEvent(true);
@ -98,19 +97,17 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_refQueue = new object[MaxRefsPerCommand * QueueCount]; _refQueue = new object[MaxRefsPerCommand * QueueCount];
} }
public void RunLoop(Action gpuLoop) public void RunLoop(ThreadStart gpuLoop)
{ {
_running = true; _running = true;
_backendThread = Thread.CurrentThread; _backendThread = Thread.CurrentThread;
_gpuThread = new Thread(() => { _gpuThread = new Thread(gpuLoop)
gpuLoop(); {
_running = false; Name = "GPU.MainThread"
_galWorkAvailable.Set(); };
});
_gpuThread.Name = "GPU.MainThread";
_gpuThread.Start(); _gpuThread.Start();
RenderLoop(); RenderLoop();
@ -120,7 +117,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
{ {
// Power through the render queue until the Gpu thread work is done. // Power through the render queue until the Gpu thread work is done.
while (_running && !_disposed) while (_running)
{ {
_galWorkAvailable.Wait(); _galWorkAvailable.Wait();
_galWorkAvailable.Reset(); _galWorkAvailable.Reset();
@ -488,12 +485,23 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return _baseRenderer.PrepareHostMapping(address, size); return _baseRenderer.PrepareHostMapping(address, size);
} }
public void FlushThreadedCommands()
{
SpinWait wait = new();
while (Volatile.Read(ref _commandCount) > 0)
{
wait.SpinOnce();
}
}
public void Dispose() public void Dispose()
{ {
// Dispose must happen from the render thread, after all commands have completed. // Dispose must happen from the render thread, after all commands have completed.
// Stop the GPU thread. // Stop the GPU thread.
_disposed = true; _running = false;
_galWorkAvailable.Set();
if (_gpuThread != null && _gpuThread.IsAlive) if (_gpuThread != null && _gpuThread.IsAlive)
{ {

View File

@ -63,6 +63,8 @@ namespace Ryujinx.Graphics.GAL
public bool PrimitiveRestartEnable; public bool PrimitiveRestartEnable;
public uint PatchControlPoints; public uint PatchControlPoints;
public DepthMode DepthMode;
public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs) public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
{ {
VertexAttribCount = vertexAttribs.Length; VertexAttribCount = vertexAttribs.Length;

View File

@ -771,7 +771,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
private void UpdateDepthMode() private void UpdateDepthMode()
{ {
_context.Renderer.Pipeline.SetDepthMode(GetDepthMode()); DepthMode mode = GetDepthMode();
_pipeline.DepthMode = mode;
_context.Renderer.Pipeline.SetDepthMode(mode);
} }
/// <summary> /// <summary>

View File

@ -390,7 +390,6 @@ namespace Ryujinx.Graphics.Gpu
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
Renderer.Dispose();
GPFifo.Dispose(); GPFifo.Dispose();
HostInitalized.Dispose(); HostInitalized.Dispose();
@ -403,6 +402,8 @@ namespace Ryujinx.Graphics.Gpu
PhysicalMemoryRegistry.Clear(); PhysicalMemoryRegistry.Clear();
RunDeferredActions(); RunDeferredActions();
Renderer.Dispose();
} }
} }
} }

View File

@ -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";

View File

@ -17,8 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly ResourceCounts _resourceCounts; private readonly ResourceCounts _resourceCounts;
private readonly int _stageIndex; private readonly int _stageIndex;
private readonly int[] _constantBufferBindings;
/// <summary> /// <summary>
/// Creates a new GPU accessor. /// Creates a new GPU accessor.
/// </summary> /// </summary>
@ -28,12 +26,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
_context = context; _context = context;
_resourceCounts = resourceCounts; _resourceCounts = resourceCounts;
_stageIndex = stageIndex; _stageIndex = stageIndex;
if (context.Capabilities.Api != TargetApi.Vulkan)
{
_constantBufferBindings = new int[Constants.TotalGpUniformBuffers];
_constantBufferBindings.AsSpan().Fill(-1);
}
} }
public int QueryBindingConstantBuffer(int index) public int QueryBindingConstantBuffer(int index)
@ -45,15 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
else else
{ {
int binding = _constantBufferBindings[index]; return _resourceCounts.UniformBuffersCount++;
if (binding < 0)
{
binding = _resourceCounts.UniformBuffersCount++;
_constantBufferBindings[index] = binding;
}
return binding;
} }
} }
@ -165,6 +149,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>

View File

@ -736,6 +736,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
return MatchesTexture(specializationState, descriptor); return MatchesTexture(specializationState, descriptor);
} }
/// <summary>
/// Populates pipeline state that doesn't exist in older caches with default values
/// based on specialization state.
/// </summary>
/// <param name="pipelineState">Pipeline state to prepare</param>
private void PreparePipelineState(ref ProgramPipelineState pipelineState)
{
if (!_compute)
{
pipelineState.DepthMode = GraphicsState.DepthMode ? DepthMode.MinusOneToOne : DepthMode.ZeroToOne;
}
}
/// <summary> /// <summary>
/// Reads shader specialization state that has been serialized. /// Reads shader specialization state that has been serialized.
/// </summary> /// </summary>
@ -776,6 +789,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
ProgramPipelineState pipelineState = default; ProgramPipelineState pipelineState = default;
dataReader.ReadWithMagicAndSize(ref pipelineState, PgpsMagic); dataReader.ReadWithMagicAndSize(ref pipelineState, PgpsMagic);
specState.PreparePipelineState(ref pipelineState);
specState.PipelineState = pipelineState; specState.PipelineState = pipelineState;
} }

View File

@ -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,

View File

@ -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)

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
} }
} }

View File

@ -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>();

View File

@ -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);
} }

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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>

View File

@ -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>();

View File

@ -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);
}
} }
} }

View File

@ -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);
}
} }
} }

View File

@ -97,6 +97,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
MemoryBarrier, MemoryBarrier,
Minimum, Minimum,
MinimumU32, MinimumU32,
Modulo,
Multiply, Multiply,
MultiplyHighS32, MultiplyHighS32,
MultiplyHighU32, MultiplyHighU32,

View File

@ -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

View File

@ -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>

View File

@ -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)
{ {
} }

View File

@ -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);
}
} }
} }

View File

@ -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);

View File

@ -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,

View File

@ -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);

View File

@ -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)));

View File

@ -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);
} }

View 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);
}
}
}
}

View File

@ -0,0 +1,11 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation
{
enum HelperFunctionName
{
TexelFetchScale,
TextureSizeUnscale
}
}

View File

@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly HashSet<int> _usedConstantBufferBindings; private readonly HashSet<int> _usedConstantBufferBindings;
public ShaderProperties Properties => _properties;
public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor, ShaderProperties properties) public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor, ShaderProperties properties)
{ {
_gpuAccessor = gpuAccessor; _gpuAccessor = gpuAccessor;
@ -98,19 +100,6 @@ namespace Ryujinx.Graphics.Shader.Translation
_properties.AddConstantBuffer(binding, new BufferDefinition(BufferLayout.Std140, 0, binding, name, type)); _properties.AddConstantBuffer(binding, new BufferDefinition(BufferLayout.Std140, 0, binding, name, type));
} }
public void InheritFrom(ResourceManager other)
{
for (int i = 0; i < other._cbSlotToBindingMap.Length; i++)
{
int binding = other._cbSlotToBindingMap[i];
if (binding >= 0)
{
_cbSlotToBindingMap[i] = binding;
}
}
}
public static string GetShaderStagePrefix(ShaderStage stage) public static string GetShaderStagePrefix(ShaderStage stage)
{ {
uint index = (uint)stage; uint index = (uint)stage;

View File

@ -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)

View File

@ -39,9 +39,9 @@ namespace Ryujinx.Graphics.Shader.Translation
public TranslationOptions Options { get; } public TranslationOptions Options { get; }
public ShaderProperties Properties { get; } public ShaderProperties Properties => ResourceManager.Properties;
public ResourceManager ResourceManager { get; } public ResourceManager ResourceManager { get; set; }
public bool TransformFeedbackEnabled { get; } public bool TransformFeedbackEnabled { get; }
@ -159,8 +159,7 @@ namespace Ryujinx.Graphics.Shader.Translation
_sbSlots = new Dictionary<int, int>(); _sbSlots = new Dictionary<int, int>();
_sbSlotsReverse = new Dictionary<int, int>(); _sbSlotsReverse = new Dictionary<int, int>();
Properties = new ShaderProperties(); ResourceManager = new ResourceManager(stage, gpuAccessor, new ShaderProperties());
ResourceManager = new ResourceManager(stage, gpuAccessor, Properties);
} }
public ShaderConfig( public ShaderConfig(
@ -429,8 +428,6 @@ namespace Ryujinx.Graphics.Shader.Translation
public void InheritFrom(ShaderConfig other) public void InheritFrom(ShaderConfig other)
{ {
ResourceManager.InheritFrom(other.ResourceManager);
ClipDistancesWritten |= other.ClipDistancesWritten; ClipDistancesWritten |= other.ClipDistancesWritten;
UsedFeatures |= other.UsedFeatures; UsedFeatures |= other.UsedFeatures;
@ -860,7 +857,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 +869,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 +894,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);
} }

View File

@ -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;
} }

View File

@ -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);

View File

@ -155,6 +155,9 @@ namespace Ryujinx.Graphics.Shader.Translation
{ {
other._config.MergeOutputUserAttributes(_config.UsedOutputAttributes, Enumerable.Empty<int>()); other._config.MergeOutputUserAttributes(_config.UsedOutputAttributes, Enumerable.Empty<int>());
// We need to share the resource manager since both shaders accesses the same constant buffers.
other._config.ResourceManager = _config.ResourceManager;
FunctionCode[] otherCode = EmitShader(other._program, other._config, initializeOutputs: true, out int aStart); FunctionCode[] otherCode = EmitShader(other._program, other._config, initializeOutputs: true, out int aStart);
code = Combine(otherCode, code, aStart); code = Combine(otherCode, code, aStart);

View File

@ -96,8 +96,6 @@ namespace Ryujinx.Graphics.Vulkan.Effects
{ {
var originalInfo = view.Info; var originalInfo = view.Info;
var swapRB = originalInfo.Format.IsBgr() && originalInfo.SwizzleR == SwizzleComponent.Red;
var info = new TextureCreateInfo( var info = new TextureCreateInfo(
width, width,
height, height,
@ -110,9 +108,9 @@ namespace Ryujinx.Graphics.Vulkan.Effects
originalInfo.Format, originalInfo.Format,
originalInfo.DepthStencilMode, originalInfo.DepthStencilMode,
originalInfo.Target, originalInfo.Target,
swapRB ? originalInfo.SwizzleB : originalInfo.SwizzleR, originalInfo.SwizzleR,
originalInfo.SwizzleG, originalInfo.SwizzleG,
swapRB ? originalInfo.SwizzleR : originalInfo.SwizzleB, originalInfo.SwizzleB,
originalInfo.SwizzleA); originalInfo.SwizzleA);
_intermediaryTexture?.Dispose(); _intermediaryTexture?.Dispose();
_intermediaryTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView; _intermediaryTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
@ -155,7 +153,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize); var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
_pipeline.SetImage(0, _intermediaryTexture, GAL.Format.R8G8B8A8Unorm); _pipeline.SetImage(0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier(); _pipeline.ComputeBarrier();

View File

@ -56,28 +56,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
if (_texture == null || _texture.Width != view.Width || _texture.Height != view.Height) if (_texture == null || _texture.Width != view.Width || _texture.Height != view.Height)
{ {
_texture?.Dispose(); _texture?.Dispose();
_texture = _renderer.CreateTexture(view.Info, view.ScaleFactor) as TextureView;
var info = view.Info;
if (view.Info.Format.IsBgr())
{
info = new TextureCreateInfo(info.Width,
info.Height,
info.Depth,
info.Levels,
info.Samples,
info.BlockWidth,
info.BlockHeight,
info.BytesPerPixel,
info.Format,
info.DepthStencilMode,
info.Target,
info.SwizzleB,
info.SwizzleG,
info.SwizzleR,
info.SwizzleA);
}
_texture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
} }
_pipeline.SetCommandBuffer(cbs); _pipeline.SetCommandBuffer(cbs);
@ -96,7 +75,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize); var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize); var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
_pipeline.SetImage(0, _texture, GAL.Format.R8G8B8A8Unorm); _pipeline.SetImage(0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_renderer.BufferManager.Delete(bufferHandle); _renderer.BufferManager.Delete(bufferHandle);

View File

@ -4,7 +4,6 @@ using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation; using Ryujinx.Graphics.Shader.Translation;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using Format = Ryujinx.Graphics.GAL.Format;
namespace Ryujinx.Graphics.Vulkan.Effects namespace Ryujinx.Graphics.Vulkan.Effects
{ {
@ -149,7 +148,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
1, 1,
1, 1,
1, 1,
Format.R8G8Unorm, GAL.Format.R8G8Unorm,
DepthStencilMode.Depth, DepthStencilMode.Depth,
Target.Texture2D, Target.Texture2D,
SwizzleComponent.Red, SwizzleComponent.Red,
@ -165,7 +164,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
1, 1,
1, 1,
1, 1,
Format.R8Unorm, GAL.Format.R8Unorm,
DepthStencilMode.Depth, DepthStencilMode.Depth,
Target.Texture2D, Target.Texture2D,
SwizzleComponent.Red, SwizzleComponent.Red,
@ -192,30 +191,9 @@ namespace Ryujinx.Graphics.Vulkan.Effects
_edgeOutputTexture?.Dispose(); _edgeOutputTexture?.Dispose();
_blendOutputTexture?.Dispose(); _blendOutputTexture?.Dispose();
var info = view.Info; _outputTexture = _renderer.CreateTexture(view.Info, view.ScaleFactor) as TextureView;
_edgeOutputTexture = _renderer.CreateTexture(view.Info, view.ScaleFactor) as TextureView;
if (view.Info.Format.IsBgr()) _blendOutputTexture = _renderer.CreateTexture(view.Info, view.ScaleFactor) as TextureView;
{
info = new TextureCreateInfo(info.Width,
info.Height,
info.Depth,
info.Levels,
info.Samples,
info.BlockWidth,
info.BlockHeight,
info.BytesPerPixel,
info.Format,
info.DepthStencilMode,
info.Target,
info.SwizzleB,
info.SwizzleG,
info.SwizzleR,
info.SwizzleA);
}
_outputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
_edgeOutputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
_blendOutputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
} }
_pipeline.SetCommandBuffer(cbs); _pipeline.SetCommandBuffer(cbs);
@ -240,7 +218,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer); _renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize); var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) }); _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
_pipeline.SetImage(0, _edgeOutputTexture, GAL.Format.R8G8B8A8Unorm); _pipeline.SetImage(0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier(); _pipeline.ComputeBarrier();
@ -250,7 +228,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
_pipeline.SetImage(0, _blendOutputTexture, GAL.Format.R8G8B8A8Unorm); _pipeline.SetImage(0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier(); _pipeline.ComputeBarrier();
@ -259,7 +237,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
_pipeline.Specialize(_specConstants); _pipeline.Specialize(_specConstants);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear); _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
_pipeline.SetImage(0, _outputTexture, GAL.Format.R8G8B8A8Unorm); _pipeline.SetImage(0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1); _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier(); _pipeline.ComputeBarrier();

View File

@ -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];

View File

@ -168,5 +168,233 @@ namespace Ryujinx.Graphics.Vulkan
{ {
return _table[(int)format]; return _table[(int)format];
} }
public static Format ConvertRgba8SrgbToUnorm(Format format)
{
return format switch
{
Format.R8G8B8A8Srgb => Format.R8G8B8A8Unorm,
Format.B8G8R8A8Srgb => Format.B8G8R8A8Unorm,
_ => 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;
}
} }
} }

View File

@ -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();

View File

@ -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;

View File

@ -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,
@ -345,7 +358,7 @@ namespace Ryujinx.Graphics.Vulkan
public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
{ {
if (!_program.IsLinked) if (!_program.IsLinked || vertexCount == 0)
{ {
return; return;
} }
@ -409,7 +422,7 @@ namespace Ryujinx.Graphics.Vulkan
public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance) public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance)
{ {
if (!_program.IsLinked) if (!_program.IsLinked || indexCount == 0)
{ {
return; return;
} }
@ -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++)
{ {

View File

@ -165,6 +165,7 @@ namespace Ryujinx.Graphics.Vulkan
pipeline.DepthTestEnable = state.DepthTest.TestEnable; pipeline.DepthTestEnable = state.DepthTest.TestEnable;
pipeline.DepthWriteEnable = state.DepthTest.WriteEnable; pipeline.DepthWriteEnable = state.DepthTest.WriteEnable;
pipeline.DepthCompareOp = state.DepthTest.Func.Convert(); pipeline.DepthCompareOp = state.DepthTest.Func.Convert();
pipeline.DepthMode = state.DepthMode == DepthMode.MinusOneToOne;
pipeline.FrontFace = state.FrontFace.Convert(); pipeline.FrontFace = state.FrontFace.Convert();
@ -294,6 +295,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 +303,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 +318,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;
} }

View File

@ -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();

View File

@ -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)
{ {

View File

@ -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;
} }

View File

@ -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();

View File

@ -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];

View File

@ -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,

View File

@ -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))

View File

@ -130,7 +130,7 @@ namespace Ryujinx.Headless.SDL2
public float AudioVolume { get; set; } public float AudioVolume { get; set; }
[Option("use-hypervisor", Required = false, Default = true, HelpText = "Uses Hypervisor over JIT if available.")] [Option("use-hypervisor", Required = false, Default = true, HelpText = "Uses Hypervisor over JIT if available.")]
public bool UseHypervisor { get; set; } public bool? UseHypervisor { get; set; }
[Option("lan-interface-id", Required = false, Default = "0", HelpText = "GUID for the network interface used by LAN.")] [Option("lan-interface-id", Required = false, Default = "0", HelpText = "GUID for the network interface used by LAN.")]
public string MultiplayerLanInterfaceId { get; set; } public string MultiplayerLanInterfaceId { get; set; }

View File

@ -9,6 +9,7 @@ using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Logging.Targets;
using Ryujinx.Common.SystemInterop; using Ryujinx.Common.SystemInterop;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.Cpu; using Ryujinx.Cpu;
@ -339,6 +340,15 @@ namespace Ryujinx.Headless.SDL2
GraphicsConfig.EnableShaderCache = true; GraphicsConfig.EnableShaderCache = true;
if (OperatingSystem.IsMacOS())
{
if (option.GraphicsBackend == GraphicsBackend.OpenGl)
{
option.GraphicsBackend = GraphicsBackend.Vulkan;
Logger.Warning?.Print(LogClass.Application, "OpenGL is not supported on macOS, switching to Vulkan!");
}
}
IGamepad gamepad; IGamepad gamepad;
if (option.ListInputIds) if (option.ListInputIds)
@ -550,7 +560,7 @@ namespace Ryujinx.Headless.SDL2
options.IgnoreMissingServices, options.IgnoreMissingServices,
options.AspectRatio, options.AspectRatio,
options.AudioVolume, options.AudioVolume,
options.UseHypervisor, options.UseHypervisor ?? true,
options.MultiplayerLanInterfaceId); options.MultiplayerLanInterfaceId);
return new Switch(configuration); return new Switch(configuration);
@ -703,4 +713,4 @@ namespace Ryujinx.Headless.SDL2
return true; return true;
} }
} }
} }

View File

@ -7,6 +7,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version> <Version>1.0.0-dirty</Version>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants> <DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
<TieredPGO>true</TieredPGO> <TieredPGO>true</TieredPGO>
</PropertyGroup> </PropertyGroup>
@ -15,6 +16,10 @@
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" /> <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<Exec Command="codesign --entitlements '$(ProjectDir)..\..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
</Target>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" /> <ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
@ -29,6 +34,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" /> <PackageReference Include="CommandLineParser" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -63,4 +69,4 @@
<PublishTrimmed>true</PublishTrimmed> <PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode> <TrimMode>partial</TrimMode>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@ -62,6 +62,7 @@ namespace Ryujinx.Headless.SDL2
private readonly long _ticksPerFrame; private readonly long _ticksPerFrame;
private readonly CancellationTokenSource _gpuCancellationTokenSource; private readonly CancellationTokenSource _gpuCancellationTokenSource;
private readonly ManualResetEvent _exitEvent; private readonly ManualResetEvent _exitEvent;
private readonly ManualResetEvent _gpuDoneEvent;
private long _ticks; private long _ticks;
private bool _isActive; private bool _isActive;
@ -91,6 +92,7 @@ namespace Ryujinx.Headless.SDL2
_ticksPerFrame = Stopwatch.Frequency / TargetFps; _ticksPerFrame = Stopwatch.Frequency / TargetFps;
_gpuCancellationTokenSource = new CancellationTokenSource(); _gpuCancellationTokenSource = new CancellationTokenSource();
_exitEvent = new ManualResetEvent(false); _exitEvent = new ManualResetEvent(false);
_gpuDoneEvent = new ManualResetEvent(false);
_aspectRatio = aspectRatio; _aspectRatio = aspectRatio;
_enableMouse = enableMouse; _enableMouse = enableMouse;
HostUiTheme = new HeadlessHostUiTheme(); HostUiTheme = new HeadlessHostUiTheme();
@ -275,6 +277,14 @@ namespace Ryujinx.Headless.SDL2
_ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame);
} }
} }
// Make sure all commands in the run loop are fully executed before leaving the loop.
if (Device.Gpu.Renderer is ThreadedRenderer threaded)
{
threaded.FlushThreadedCommands();
}
_gpuDoneEvent.Set();
}); });
FinalizeWindowRenderer(); FinalizeWindowRenderer();
@ -404,7 +414,10 @@ namespace Ryujinx.Headless.SDL2
MainLoop(); MainLoop();
renderLoopThread.Join(); // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
// We only need to wait for all commands submitted during the main gpu loop to be processed.
_gpuDoneEvent.WaitOne();
_gpuDoneEvent.Dispose();
nvStutterWorkaround?.Join(); nvStutterWorkaround?.Join();
Exit(); Exit();

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Text; using System.Text;
@ -53,9 +54,9 @@ namespace Ryujinx.Memory
IntPtr ptr = mmap(IntPtr.Zero, size, prot, flags, -1, 0); IntPtr ptr = mmap(IntPtr.Zero, size, prot, flags, -1, 0);
if (ptr == new IntPtr(-1L)) if (ptr == MAP_FAILED)
{ {
throw new OutOfMemoryException(); throw new SystemException(Marshal.GetLastPInvokeErrorMessage());
} }
if (!_allocations.TryAdd(ptr, size)) if (!_allocations.TryAdd(ptr, size))
@ -76,17 +77,33 @@ namespace Ryujinx.Memory
prot |= MmapProts.PROT_EXEC; prot |= MmapProts.PROT_EXEC;
} }
return mprotect(address, size, prot) == 0; if (mprotect(address, size, prot) != 0)
{
throw new SystemException(Marshal.GetLastPInvokeErrorMessage());
}
return true;
} }
public static bool Decommit(IntPtr address, ulong size) public static bool Decommit(IntPtr address, ulong size)
{ {
// Must be writable for madvise to work properly. // Must be writable for madvise to work properly.
mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE); if (mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) != 0)
{
throw new SystemException(Marshal.GetLastPInvokeErrorMessage());
}
madvise(address, size, MADV_REMOVE); if (madvise(address, size, MADV_REMOVE) != 0)
{
throw new SystemException(Marshal.GetLastPInvokeErrorMessage());
}
return mprotect(address, size, MmapProts.PROT_NONE) == 0; if (mprotect(address, size, MmapProts.PROT_NONE) != 0)
{
throw new SystemException(Marshal.GetLastPInvokeErrorMessage());
}
return true;
} }
public static bool Reprotect(IntPtr address, ulong size, MemoryPermission permission) public static bool Reprotect(IntPtr address, ulong size, MemoryPermission permission)

View File

@ -1,5 +1,6 @@
using Ryujinx.Memory.WindowsShared; using Ryujinx.Memory.WindowsShared;
using System; using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
namespace Ryujinx.Memory namespace Ryujinx.Memory
@ -36,7 +37,7 @@ namespace Ryujinx.Memory
if (ptr == IntPtr.Zero) if (ptr == IntPtr.Zero)
{ {
throw new OutOfMemoryException(); throw new SystemException(Marshal.GetLastPInvokeErrorMessage());
} }
return ptr; return ptr;
@ -48,7 +49,7 @@ namespace Ryujinx.Memory
if (ptr == IntPtr.Zero) if (ptr == IntPtr.Zero)
{ {
throw new OutOfMemoryException(); throw new SystemException(Marshal.GetLastPInvokeErrorMessage());
} }
return ptr; return ptr;

Some files were not shown because too many files have changed in this diff Show More