Compare commits

..

59 Commits

Author SHA1 Message Date
0003a7c118 Vulkan: Use aspect flags for identity views for bindings (#5267) 2023-06-08 20:23:36 -03:00
2cdcfe46d8 Remove barrier on Intel if control flow is potentially divergent (#5044)
* Remove barrier on Intel if control flow is potentially divergent

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

* Shader cache version bump

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

* Replicate logic in Avalonia version.

* Address requested changes

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

* Replicate logic in Avalonia version.

* Address requested changes

* Address requested changes

* Address requested changes

* Comment cleanup

* Address feedback

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

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

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

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

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

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

* Use TryParse

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

* replacing ZbcColorArray with Array4<uint>

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

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

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

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

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

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

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

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

* Improve wording

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

* Update GTK UI to check text against KeyboardMode

* Update Ava UI to check text against KeyboardMode

* Restructure input validation

* true when text is not empty

* Add English validation text for SoftwareKeyboardMode

* Add Chinese validation text for SoftwareKeyboardMode

* Update base on feedback

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2023-06-04 05:30:24 +02:00
21c9ac6240 Implement shader storage buffer operations using new Load/Store instructions (#4993)
* Implement storage buffer operations using new Load/Store instruction

* Extend GenerateMultiTargetStorageOp to also match access with constant offset, and log and comments

* Remove now unused code

* Catch more complex cases of global memory usage

* Shader cache version bump

* Extend global access elimination to work with more shared memory cases

* Change alignment requirement from 16 bytes to 8 bytes, handle cases where we need more than 16 storage buffers

* Tweak preferencing to catch more cases

* Enable CB0 elimination even when host storage buffer alignment is > 16 (for Intel)

* Fix storage buffer bindings

* Simplify some code

* Shader cache version bump

* Fix typo

* Extend global memory elimination to handle shared memory with multiple possible offsets and local memory
2023-06-03 20:12:18 -03:00
81c9052847 ava: Fix Input Touch (#5204) 2023-06-03 16:03:42 +01:00
9367e3c35d ava: Fix Open Applet menu enabled (#5206)
Currently, the `Open Applet` menu is still enabled when a guest is running, which is wrong. This is not fixed by refreshing the property binding on `IsEnabled`.
2023-06-03 11:03:34 +02:00
52cf141874 Armeilleure: Fix support for Windows on ARM64 (#5202)
* Armeilleure: Fix support for Windows on ARM64

Tested on Windows DevKit 2023.

* Address gdkchan's comments
2023-06-03 10:23:51 +02:00
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
c2e4c8f98e Bump MVK Version (#5057) 2023-05-22 14:41:08 -03:00
b53e7ffd46 Ava UI: Input Menu Redesign (#4990)
* Cleanup

* Remove redundant locales

* Start SVG Fixes…

Better +/- buttons

Fix the grips

Bumpers

Better directional pad

More SVG stuff

Grip adjustments

Final stuff

* Make image bigger

* Border radius

* More cleanup

* Restructure

* Restructure Rumble View

* Use compiled bindings where possible

* Round those pesky corners

* Ack Suggestions

* More suggestions

* Update src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-05-22 01:16:20 +02:00
170 changed files with 4481 additions and 2675 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" />

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

@ -6,10 +6,11 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace ARMeilleure.Translation.Cache namespace ARMeilleure.Translation.Cache
{ {
static class JitCache static partial class JitCache
{ {
private const int PageSize = 4 * 1024; private const int PageSize = 4 * 1024;
private const int PageMask = PageSize - 1; private const int PageMask = PageSize - 1;
@ -27,6 +28,10 @@ namespace ARMeilleure.Translation.Cache
private static readonly object _lock = new object(); private static readonly object _lock = new object();
private static bool _initialized; private static bool _initialized;
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize);
public static void Initialize(IJitMemoryAllocator allocator) public static void Initialize(IJitMemoryAllocator allocator)
{ {
if (_initialized) return; if (_initialized) return;
@ -36,7 +41,11 @@ namespace ARMeilleure.Translation.Cache
if (_initialized) return; if (_initialized) return;
_jitRegion = new ReservedRegion(allocator, CacheSize); _jitRegion = new ReservedRegion(allocator, CacheSize);
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
}
_cacheAllocator = new CacheMemoryAllocator(CacheSize); _cacheAllocator = new CacheMemoryAllocator(CacheSize);
@ -77,7 +86,14 @@ namespace ARMeilleure.Translation.Cache
Marshal.Copy(code, 0, funcPtr, code.Length); Marshal.Copy(code, 0, funcPtr, code.Length);
ReprotectAsExecutable(funcOffset, code.Length); ReprotectAsExecutable(funcOffset, code.Length);
_jitCacheInvalidator.Invalidate(funcPtr, (ulong)code.Length); if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (UIntPtr)code.Length);
}
else
{
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
}
} }
Add(funcOffset, code.Length, func.UnwindInfo); Add(funcOffset, code.Length, func.UnwindInfo);

View File

@ -47,8 +47,8 @@ namespace ARMeilleure.Translation.Cache
public JitCacheInvalidation(IJitMemoryAllocator allocator) public JitCacheInvalidation(IJitMemoryAllocator allocator)
{ {
// On macOS, a different path is used to write to the JIT cache, which does the invalidation. // On macOS and Windows, a different path is used to write to the JIT cache, which does the invalidation.
if (!OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{ {
ulong size = (ulong)_invalidationCode.Length * sizeof(int); ulong size = (ulong)_invalidationCode.Length * sizeof(int);
ulong mask = (ulong)ReservedRegion.DefaultGranularity - 1; ulong mask = (ulong)ReservedRegion.DefaultGranularity - 1;

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",
@ -216,26 +223,17 @@
"ControllerSettingsDPadDown": "Down", "ControllerSettingsDPadDown": "Down",
"ControllerSettingsDPadLeft": "Left", "ControllerSettingsDPadLeft": "Left",
"ControllerSettingsDPadRight": "Right", "ControllerSettingsDPadRight": "Right",
"ControllerSettingsStickButton": "Button",
"ControllerSettingsStickUp": "Up",
"ControllerSettingsStickDown": "Down",
"ControllerSettingsStickLeft": "Left",
"ControllerSettingsStickRight": "Right",
"ControllerSettingsStickStick": "Stick",
"ControllerSettingsStickInvertXAxis": "Invert Stick X",
"ControllerSettingsStickInvertYAxis": "Invert Stick Y",
"ControllerSettingsStickDeadzone": "Deadzone:",
"ControllerSettingsLStick": "Left Stick", "ControllerSettingsLStick": "Left Stick",
"ControllerSettingsLStickButton": "Button",
"ControllerSettingsLStickUp": "Up",
"ControllerSettingsLStickDown": "Down",
"ControllerSettingsLStickLeft": "Left",
"ControllerSettingsLStickRight": "Right",
"ControllerSettingsLStickStick": "Stick",
"ControllerSettingsLStickInvertXAxis": "Invert Stick X",
"ControllerSettingsLStickInvertYAxis": "Invert Stick Y",
"ControllerSettingsLStickDeadzone": "Deadzone:",
"ControllerSettingsRStick": "Right Stick", "ControllerSettingsRStick": "Right Stick",
"ControllerSettingsRStickButton": "Button",
"ControllerSettingsRStickUp": "Up",
"ControllerSettingsRStickDown": "Down",
"ControllerSettingsRStickLeft": "Left",
"ControllerSettingsRStickRight": "Right",
"ControllerSettingsRStickStick": "Stick",
"ControllerSettingsRStickInvertXAxis": "Invert Stick X",
"ControllerSettingsRStickInvertYAxis": "Invert Stick Y",
"ControllerSettingsRStickDeadzone": "Deadzone:",
"ControllerSettingsTriggersLeft": "Triggers Left", "ControllerSettingsTriggersLeft": "Triggers Left",
"ControllerSettingsTriggersRight": "Triggers Right", "ControllerSettingsTriggersRight": "Triggers Right",
"ControllerSettingsTriggersButtonsLeft": "Trigger Buttons Left", "ControllerSettingsTriggersButtonsLeft": "Trigger Buttons Left",
@ -291,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",
@ -545,6 +544,9 @@
"SwkbdMinCharacters": "Must be at least {0} characters long", "SwkbdMinCharacters": "Must be at least {0} characters long",
"SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long",
"SoftwareKeyboard": "Software Keyboard", "SoftwareKeyboard": "Software Keyboard",
"SoftwareKeyboardModeNumbersOnly": "Must be numbers only",
"SoftwareKeyboardModeAlphabet": "Must be alphabets only",
"SoftwareKeyboardModeASCII": "Must be ASCII text only",
"DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", "DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
"DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", "DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
"DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n", "DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n",
@ -629,7 +631,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:",
@ -641,10 +643,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

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

View File

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

View File

@ -1,7 +1,6 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using FluentAvalonia.Core;
using Ryujinx.Input; using Ryujinx.Input;
using System; using System;
using System.Numerics; using System.Numerics;
@ -30,14 +29,14 @@ namespace Ryujinx.Ava.Input
_window = window; _window = window;
_widget.PointerMoved += Parent_PointerMovedEvent; _widget.PointerMoved += Parent_PointerMovedEvent;
_widget.PointerPressed += Parent_PointerPressEvent; _widget.PointerPressed += Parent_PointerPressedEvent;
_widget.PointerReleased += Parent_PointerReleaseEvent; _widget.PointerReleased += Parent_PointerReleasedEvent;
_widget.PointerWheelChanged += Parent_ScrollEvent; _widget.PointerWheelChanged += Parent_PointerWheelChanged;
_window.PointerMoved += Parent_PointerMovedEvent; _window.PointerMoved += Parent_PointerMovedEvent;
_window.PointerPressed += Parent_PointerPressEvent; _window.PointerPressed += Parent_PointerPressedEvent;
_window.PointerReleased += Parent_PointerReleaseEvent; _window.PointerReleased += Parent_PointerReleasedEvent;
_window.PointerWheelChanged += Parent_ScrollEvent; _window.PointerWheelChanged += Parent_PointerWheelChanged;
PressedButtons = new bool[(int)MouseButton.Count]; PressedButtons = new bool[(int)MouseButton.Count];
@ -63,29 +62,25 @@ namespace Ryujinx.Ava.Input
_size = new Size((int)rect.Width, (int)rect.Height); _size = new Size((int)rect.Width, (int)rect.Height);
} }
private void Parent_ScrollEvent(object o, PointerWheelEventArgs args) private void Parent_PointerWheelChanged(object o, PointerWheelEventArgs args)
{ {
Scroll = new Vector2((float)args.Delta.X, (float)args.Delta.Y); Scroll = new Vector2((float)args.Delta.X, (float)args.Delta.Y);
} }
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args) private void Parent_PointerReleasedEvent(object o, PointerReleasedEventArgs args)
{ {
if (args.InitialPressMouseButton != Avalonia.Input.MouseButton.None) uint button = (uint)args.InitialPressMouseButton - 1;
{
int button = (int)args.InitialPressMouseButton;
if (PressedButtons.Count() >= button) if ((uint)PressedButtons.Length > button)
{ {
PressedButtons[button] = false; PressedButtons[button] = false;
}
} }
} }
private void Parent_PointerPressedEvent(object o, PointerPressedEventArgs args)
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
{ {
int button = (int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind; uint button = (uint)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind;
if (PressedButtons.Count() >= button) if ((uint)PressedButtons.Length > button)
{ {
PressedButtons[button] = true; PressedButtons[button] = true;
} }
@ -100,17 +95,17 @@ namespace Ryujinx.Ava.Input
public void SetMousePressed(MouseButton button) public void SetMousePressed(MouseButton button)
{ {
if (PressedButtons.Count() >= (int)button) if ((uint)PressedButtons.Length > (uint)button)
{ {
PressedButtons[(int)button] = true; PressedButtons[(uint)button] = true;
} }
} }
public void SetMouseReleased(MouseButton button) public void SetMouseReleased(MouseButton button)
{ {
if (PressedButtons.Count() >= (int)button) if ((uint)PressedButtons.Length > (uint)button)
{ {
PressedButtons[(int)button] = false; PressedButtons[(uint)button] = false;
} }
} }
@ -121,9 +116,9 @@ namespace Ryujinx.Ava.Input
public bool IsButtonPressed(MouseButton button) public bool IsButtonPressed(MouseButton button)
{ {
if (PressedButtons.Count() >= (int)button) if ((uint)PressedButtons.Length > (uint)button)
{ {
return PressedButtons[(int)button]; return PressedButtons[(uint)button];
} }
return false; return false;
@ -149,14 +144,14 @@ namespace Ryujinx.Ava.Input
_isDisposed = true; _isDisposed = true;
_widget.PointerMoved -= Parent_PointerMovedEvent; _widget.PointerMoved -= Parent_PointerMovedEvent;
_widget.PointerPressed -= Parent_PointerPressEvent; _widget.PointerPressed -= Parent_PointerPressedEvent;
_widget.PointerReleased -= Parent_PointerReleaseEvent; _widget.PointerReleased -= Parent_PointerReleasedEvent;
_widget.PointerWheelChanged -= Parent_ScrollEvent; _widget.PointerWheelChanged -= Parent_PointerWheelChanged;
_window.PointerMoved -= Parent_PointerMovedEvent; _window.PointerMoved -= Parent_PointerMovedEvent;
_window.PointerPressed -= Parent_PointerPressEvent; _window.PointerPressed -= Parent_PointerPressedEvent;
_window.PointerReleased -= Parent_PointerReleaseEvent; _window.PointerReleased -= Parent_PointerReleasedEvent;
_window.PointerWheelChanged -= Parent_ScrollEvent; _window.PointerWheelChanged -= Parent_PointerWheelChanged;
_widget = null; _widget = null;
} }

View File

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

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

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

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

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

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

@ -7,6 +7,7 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input; using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Views.Input;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
@ -30,7 +31,7 @@ using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
public class ControllerSettingsViewModel : BaseModel, IDisposable public class ControllerInputViewModel : BaseModel, IDisposable
{ {
private const string Disabled = "disabled"; private const string Disabled = "disabled";
private const string ProControllerResource = "Ryujinx.Ui.Common/Resources/Controller_ProCon.svg"; private const string ProControllerResource = "Ryujinx.Ui.Common/Resources/Controller_ProCon.svg";
@ -231,7 +232,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public InputConfig Config { get; set; } public InputConfig Config { get; set; }
public ControllerSettingsViewModel(UserControl owner) : this() public ControllerInputViewModel(UserControl owner) : this()
{ {
_owner = owner; _owner = owner;
@ -258,7 +259,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public ControllerSettingsViewModel() public ControllerInputViewModel()
{ {
PlayerIndexes = new ObservableCollection<PlayerModel>(); PlayerIndexes = new ObservableCollection<PlayerModel>();
Controllers = new ObservableCollection<ControllerModel>(); Controllers = new ObservableCollection<ControllerModel>();
@ -328,12 +329,12 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void ShowMotionConfig() public async void ShowMotionConfig()
{ {
await MotionSettingsWindow.Show(this); await MotionInputView.Show(this);
} }
public async void ShowRumbleConfig() public async void ShowRumbleConfig()
{ {
await RumbleSettingsWindow.Show(this); await RumbleInputView.Show(this);
} }
private void LoadInputDriver() private void LoadInputDriver()

View File

@ -257,6 +257,7 @@ namespace Ryujinx.Ava.UI.ViewModels
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(EnableNonGameRunningControls)); OnPropertyChanged(nameof(EnableNonGameRunningControls));
OnPropertyChanged(nameof(IsAppletMenuActive));
OnPropertyChanged(nameof(StatusBarVisible)); OnPropertyChanged(nameof(StatusBarVisible));
OnPropertyChanged(nameof(ShowFirmwareStatus)); OnPropertyChanged(nameof(ShowFirmwareStatus));
} }
@ -1529,6 +1530,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

@ -0,0 +1,93 @@
namespace Ryujinx.Ava.UI.ViewModels
{
public class MotionInputViewModel : BaseModel
{
private int _slot;
public int Slot
{
get => _slot;
set
{
_slot = value;
OnPropertyChanged();
}
}
private int _altSlot;
public int AltSlot
{
get => _altSlot;
set
{
_altSlot = value;
OnPropertyChanged();
}
}
private string _dsuServerHost;
public string DsuServerHost
{
get => _dsuServerHost;
set
{
_dsuServerHost = value;
OnPropertyChanged();
}
}
private int _dsuServerPort;
public int DsuServerPort
{
get => _dsuServerPort;
set
{
_dsuServerPort = value;
OnPropertyChanged();
}
}
private bool _mirrorInput;
public bool MirrorInput
{
get => _mirrorInput;
set
{
_mirrorInput = value;
OnPropertyChanged();
}
}
private int _sensitivity;
public int Sensitivity
{
get => _sensitivity;
set
{
_sensitivity = value;
OnPropertyChanged();
}
}
private double _gryoDeadzone;
public double GyroDeadzone
{
get => _gryoDeadzone;
set
{
_gryoDeadzone = value;
OnPropertyChanged();
}
}
private bool _enableCemuHookMotion;
public bool EnableCemuHookMotion
{
get => _enableCemuHookMotion;
set
{
_enableCemuHookMotion = value;
OnPropertyChanged();
}
}
}
}

View File

@ -0,0 +1,27 @@
namespace Ryujinx.Ava.UI.ViewModels
{
public class RumbleInputViewModel : BaseModel
{
private float _strongRumble;
public float StrongRumble
{
get => _strongRumble;
set
{
_strongRumble = value;
OnPropertyChanged();
}
}
private float _weakRumble;
public float WeakRumble
{
get => _weakRumble;
set
{
_weakRumble = value;
OnPropertyChanged();
}
}
}
}

View File

@ -4,7 +4,6 @@ using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
@ -13,18 +12,18 @@ using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using System; using System;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Views.Input
{ {
public partial class ControllerSettingsWindow : UserControl public partial class ControllerInputView : UserControl
{ {
private bool _dialogOpen; private bool _dialogOpen;
private ButtonKeyAssigner _currentAssigner; private ButtonKeyAssigner _currentAssigner;
internal ControllerSettingsViewModel ViewModel { get; set; } internal ControllerInputViewModel ViewModel { get; set; }
public ControllerSettingsWindow() public ControllerInputView()
{ {
DataContext = ViewModel = new ControllerSettingsViewModel(this); DataContext = ViewModel = new ControllerInputViewModel(this);
InitializeComponent(); InitializeComponent();

View File

@ -1,12 +1,15 @@
<UserControl <UserControl
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:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d" mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Windows.MotionSettingsWindow" x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView"
x:CompileBindings="True"
x:DataType="viewModels:MotionInputViewModel"
Focusable="True"> Focusable="True">
<Grid Margin="10"> <Grid Margin="10">
<Grid.RowDefinitions> <Grid.RowDefinitions>
@ -14,7 +17,9 @@
<RowDefinition /> <RowDefinition />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <StackPanel
Orientation="Horizontal"
HorizontalAlignment="Center">
<TextBlock <TextBlock
Margin="0" Margin="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
@ -28,11 +33,14 @@
Maximum="100" Maximum="100"
Minimum="0" Minimum="0"
Value="{Binding Sensitivity, Mode=TwoWay}" /> Value="{Binding Sensitivity, Mode=TwoWay}" />
<TextBlock HorizontalAlignment="Center" <TextBlock
Margin="5, 0" HorizontalAlignment="Center"
Text="{Binding Sensitivity, StringFormat=\{0:0\}%}" /> Margin="5, 0"
Text="{Binding Sensitivity, StringFormat=\{0:0\}%}" />
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <StackPanel
Orientation="Horizontal"
HorizontalAlignment="Center">
<TextBlock <TextBlock
Margin="0" Margin="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
@ -51,17 +59,25 @@
Margin="5, 0" Margin="5, 0"
Text="{Binding GyroDeadzone, StringFormat=\{0:0.00\}}" /> Text="{Binding GyroDeadzone, StringFormat=\{0:0.00\}}" />
</StackPanel> </StackPanel>
<Separator Height="1" Margin="0,5" /> <Separator
<CheckBox Margin="5" IsChecked="{Binding EnableCemuHookMotion}"> Height="1"
<TextBlock Margin="0,3,0,0" VerticalAlignment="Center" Margin="0,5" />
Text="{locale:Locale ControllerSettingsMotionUseCemuhookCompatibleMotion}" /> <CheckBox
Margin="5"
IsChecked="{Binding EnableCemuHookMotion}">
<TextBlock
Margin="0,3,0,0"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionUseCemuhookCompatibleMotion}" />
</CheckBox> </CheckBox>
</StackPanel> </StackPanel>
<Border Grid.Row="1" <Border
Padding="20,5" Grid.Row="1"
BorderBrush="{DynamicResource ThemeControlBorderColor}" Padding="20,5"
BorderThickness="1" BorderBrush="{DynamicResource ThemeControlBorderColor}"
HorizontalAlignment="Stretch"> BorderThickness="1"
CornerRadius="5"
HorizontalAlignment="Stretch">
<Grid VerticalAlignment="Top"> <Grid VerticalAlignment="Top">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@ -109,30 +125,42 @@
<ColumnDefinition /> <ColumnDefinition />
<ColumnDefinition /> <ColumnDefinition />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Margin="0,10,0,0" VerticalAlignment="Center" <TextBlock
Text="{locale:Locale ControllerSettingsMotionControllerSlot}" /> Margin="0,10,0,0"
<ui:NumberBox Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"
Name="CemuHookSlotUpDown" Text="{locale:Locale ControllerSettingsMotionControllerSlot}" />
SmallChange="1" <ui:NumberBox
LargeChange="1" Grid.Row="0"
Maximum="4" Grid.Column="1"
Minimum="0" Name="CemuHookSlotUpDown"
Value="{Binding Slot}" /> SmallChange="1"
<TextBlock Margin="0,10,0,0" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" LargeChange="1"
Text="{locale:Locale ControllerSettingsMotionRightJoyConSlot}" /> Maximum="4"
<ui:NumberBox Grid.Row="1" Grid.Column="1" Minimum="0"
Name="CemuHookRightJoyConSlotUpDown" Value="{Binding Slot}" />
SmallChange="1" <TextBlock
LargeChange="1" Margin="0,10,0,0"
Maximum="4" Grid.Row="1"
Minimum="0" Grid.Column="0"
Value="{Binding AltSlot}" /> VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionRightJoyConSlot}" />
<ui:NumberBox
Grid.Row="1"
Grid.Column="1"
Name="CemuHookRightJoyConSlotUpDown"
SmallChange="1"
LargeChange="1"
Maximum="4"
Minimum="0"
Value="{Binding AltSlot}" />
</Grid> </Grid>
</StackPanel> </StackPanel>
<CheckBox HorizontalAlignment="Center" <CheckBox
IsChecked="{Binding MirrorInput, Mode=TwoWay}"> HorizontalAlignment="Center"
<TextBlock HorizontalAlignment="Center" IsChecked="{Binding MirrorInput, Mode=TwoWay}">
Text="{locale:Locale ControllerSettingsMotionMirrorInput}" /> <TextBlock
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionMirrorInput}" />
</CheckBox> </CheckBox>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -6,44 +6,42 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Views.Input
{ {
public partial class MotionSettingsWindow : UserControl public partial class MotionInputView : UserControl
{ {
private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel; private MotionInputViewModel _viewModel;
public MotionSettingsWindow() public MotionInputView()
{ {
InitializeComponent(); InitializeComponent();
DataContext = _viewmodel;
} }
public MotionSettingsWindow(ControllerSettingsViewModel viewmodel) public MotionInputView(ControllerInputViewModel viewModel)
{ {
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
_viewmodel = new InputConfiguration<GamepadInputId, StickInputId>() _viewModel = new MotionInputViewModel
{ {
Slot = config.Slot, Slot = config.Slot,
AltSlot = config.AltSlot, AltSlot = config.AltSlot,
DsuServerHost = config.DsuServerHost, DsuServerHost = config.DsuServerHost,
DsuServerPort = config.DsuServerPort, DsuServerPort = config.DsuServerPort,
MirrorInput = config.MirrorInput, MirrorInput = config.MirrorInput,
EnableMotion = config.EnableMotion,
Sensitivity = config.Sensitivity, Sensitivity = config.Sensitivity,
GyroDeadzone = config.GyroDeadzone, GyroDeadzone = config.GyroDeadzone,
EnableCemuHookMotion = config.EnableCemuHookMotion EnableCemuHookMotion = config.EnableCemuHookMotion
}; };
InitializeComponent(); InitializeComponent();
DataContext = _viewmodel; DataContext = _viewModel;
} }
public static async Task Show(ControllerSettingsViewModel viewmodel) public static async Task Show(ControllerInputViewModel viewModel)
{ {
MotionSettingsWindow content = new MotionSettingsWindow(viewmodel); MotionInputView content = new(viewModel);
ContentDialog contentDialog = new ContentDialog ContentDialog contentDialog = new()
{ {
Title = LocaleManager.Instance[LocaleKeys.ControllerMotionTitle], Title = LocaleManager.Instance[LocaleKeys.ControllerMotionTitle],
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave], PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave],
@ -53,16 +51,15 @@ namespace Ryujinx.Ava.UI.Windows
}; };
contentDialog.PrimaryButtonClick += (sender, args) => contentDialog.PrimaryButtonClick += (sender, args) =>
{ {
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.Slot = content._viewmodel.Slot; config.Slot = content._viewModel.Slot;
config.EnableMotion = content._viewmodel.EnableMotion; config.Sensitivity = content._viewModel.Sensitivity;
config.Sensitivity = content._viewmodel.Sensitivity; config.GyroDeadzone = content._viewModel.GyroDeadzone;
config.GyroDeadzone = content._viewmodel.GyroDeadzone; config.AltSlot = content._viewModel.AltSlot;
config.AltSlot = content._viewmodel.AltSlot; config.DsuServerHost = content._viewModel.DsuServerHost;
config.DsuServerHost = content._viewmodel.DsuServerHost; config.DsuServerPort = content._viewModel.DsuServerPort;
config.DsuServerPort = content._viewmodel.DsuServerPort; config.EnableCemuHookMotion = content._viewModel.EnableCemuHookMotion;
config.EnableCemuHookMotion = content._viewmodel.EnableCemuHookMotion; config.MirrorInput = content._viewModel.MirrorInput;
config.MirrorInput = content._viewmodel.MirrorInput;
}; };
await contentDialog.ShowAsync(); await contentDialog.ShowAsync();

View File

@ -1,11 +1,14 @@
<UserControl <UserControl
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:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d" mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Windows.RumbleSettingsWindow" x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView"
x:DataType="viewModels:RumbleInputViewModel"
x:CompileBindings="True"
Focusable="True"> Focusable="True">
<Grid Margin="10"> <Grid Margin="10">
<Grid.RowDefinitions> <Grid.RowDefinitions>

View File

@ -6,36 +6,37 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Views.Input
{ {
public partial class RumbleSettingsWindow : UserControl public partial class RumbleInputView : UserControl
{ {
private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel; private RumbleInputViewModel _viewModel;
public RumbleSettingsWindow() public RumbleInputView()
{ {
InitializeComponent(); InitializeComponent();
DataContext = _viewmodel;
} }
public RumbleSettingsWindow(ControllerSettingsViewModel viewmodel) public RumbleInputView(ControllerInputViewModel viewModel)
{ {
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
_viewmodel = new InputConfiguration<GamepadInputId, StickInputId>() _viewModel = new RumbleInputViewModel
{ {
StrongRumble = config.StrongRumble, WeakRumble = config.WeakRumble StrongRumble = config.StrongRumble,
WeakRumble = config.WeakRumble
}; };
InitializeComponent(); InitializeComponent();
DataContext = _viewmodel;
DataContext = _viewModel;
} }
public static async Task Show(ControllerSettingsViewModel viewmodel) public static async Task Show(ControllerInputViewModel viewModel)
{ {
RumbleSettingsWindow content = new RumbleSettingsWindow(viewmodel); RumbleInputView content = new(viewModel);
ContentDialog contentDialog = new ContentDialog ContentDialog contentDialog = new()
{ {
Title = LocaleManager.Instance[LocaleKeys.ControllerRumbleTitle], Title = LocaleManager.Instance[LocaleKeys.ControllerRumbleTitle],
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave], PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave],
@ -43,14 +44,14 @@ namespace Ryujinx.Ava.UI.Windows
CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose], CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose],
Content = content, Content = content,
}; };
contentDialog.PrimaryButtonClick += (sender, args) => contentDialog.PrimaryButtonClick += (sender, args) =>
{ {
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>; var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.StrongRumble = content._viewmodel.StrongRumble; config.StrongRumble = content._viewModel.StrongRumble;
config.WeakRumble = content._viewmodel.WeakRumble; config.WeakRumble = content._viewModel.WeakRumble;
}; };
await contentDialog.ShowAsync(); await contentDialog.ShowAsync();
} }
} }

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

@ -1,11 +1,11 @@
<UserControl <UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView" x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
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:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" xmlns:views="clr-namespace:Ryujinx.Ava.UI.Views.Input"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d" mc:Ignorable="d"
x:CompileBindings="True" x:CompileBindings="True"
@ -13,34 +13,56 @@
<Design.DataContext> <Design.DataContext>
<viewModels:SettingsViewModel /> <viewModels:SettingsViewModel />
</Design.DataContext> </Design.DataContext>
<ScrollViewer <ScrollViewer
Name="InputPage" Name="InputPage"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
<Border Classes="settings"> <Border Classes="settings">
<StackPanel Margin="4" Orientation="Vertical"> <Panel
<StackPanel Orientation="Horizontal"> Margin="10">
<CheckBox Margin="5,0" <Grid>
ToolTip.Tip="{locale:Locale DockModeToggleTooltip}" <Grid.RowDefinitions>
IsChecked="{Binding EnableDockedMode}"> <RowDefinition Height="Auto"/>
<TextBlock VerticalAlignment="Center" <RowDefinition Height="*" />
Text="{locale:Locale SettingsTabInputEnableDockedMode}" /> <RowDefinition Height="Auto" />
</CheckBox> </Grid.RowDefinitions>
<CheckBox Margin="5,0" <views:ControllerInputView
ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}" Grid.Row="0"
IsChecked="{Binding EnableKeyboard}"> Name="ControllerSettings" />
<TextBlock Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" /> <StackPanel
</CheckBox> Orientation="Vertical"
<CheckBox Margin="5,0" Grid.Row="2">
ToolTip.Tip="{locale:Locale DirectMouseTooltip}" <Separator
IsChecked="{Binding EnableMouse}"> Margin="0 10"
<TextBlock Text="{locale:Locale SettingsTabInputDirectMouseAccess}" /> Height="1" />
</CheckBox> <StackPanel
</StackPanel> Orientation="Horizontal"
<window:ControllerSettingsWindow Name="ControllerSettings" Margin="0" MinHeight="600" /> Spacing="10">
</StackPanel> <CheckBox
ToolTip.Tip="{locale:Locale DockModeToggleTooltip}"
MinWidth="0"
IsChecked="{Binding EnableDockedMode}">
<TextBlock
Text="{locale:Locale SettingsTabInputEnableDockedMode}" />
</CheckBox>
<CheckBox
ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}"
IsChecked="{Binding EnableKeyboard}">
<TextBlock
Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" />
</CheckBox>
<CheckBox
ToolTip.Tip="{locale:Locale DirectMouseTooltip}"
IsChecked="{Binding EnableMouse}">
<TextBlock
Text="{locale:Locale SettingsTabInputDirectMouseAccess}" />
</CheckBox>
</StackPanel>
</StackPanel>
</Grid>
</Panel>
</Border> </Border>
</ScrollViewer> </ScrollViewer>
</UserControl> </UserControl>

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

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

View File

@ -34,11 +34,14 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsCubemapView; public readonly bool SupportsCubemapView;
public readonly bool SupportsNonConstantTextureOffset; public readonly bool SupportsNonConstantTextureOffset;
public readonly bool SupportsShaderBallot; public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsShaderFloat64;
public readonly bool SupportsTextureShadowLod; public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsViewportIndexVertexTessellation; public readonly bool SupportsViewportIndexVertexTessellation;
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;
@ -80,11 +83,14 @@ namespace Ryujinx.Graphics.GAL
bool supportsCubemapView, bool supportsCubemapView,
bool supportsNonConstantTextureOffset, bool supportsNonConstantTextureOffset,
bool supportsShaderBallot, bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
bool supportsShaderFloat64,
bool supportsTextureShadowLod, bool supportsTextureShadowLod,
bool supportsViewportIndexVertexTessellation, bool supportsViewportIndexVertexTessellation,
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,
@ -122,11 +128,14 @@ namespace Ryujinx.Graphics.GAL
SupportsCubemapView = supportsCubemapView; SupportsCubemapView = supportsCubemapView;
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
SupportsShaderBallot = supportsShaderBallot; SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsShaderFloat64 = supportsShaderFloat64;
SupportsTextureShadowLod = supportsTextureShadowLod; SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation; SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
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

@ -80,11 +80,6 @@ namespace Ryujinx.Graphics.Gpu
/// </summary> /// </summary>
public const int GobAlignment = 64; public const int GobAlignment = 64;
/// <summary>
/// Expected byte alignment for storage buffers
/// </summary>
public const int StorageAlignment = 16;
/// <summary> /// <summary>
/// Number of the uniform buffer reserved by the driver to store the storage buffer base addresses. /// Number of the uniform buffer reserved by the driver to store the storage buffer base addresses.
/// </summary> /// </summary>

View File

@ -151,8 +151,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
ShaderProgramInfo info = cs.Shaders[0].Info; ShaderProgramInfo info = cs.Shaders[0].Info;
bool hasUnaligned = _channel.BufferManager.HasUnalignedStorageBuffers;
for (int index = 0; index < info.SBuffers.Count; index++) for (int index = 0; index < info.SBuffers.Count; index++)
{ {
BufferDescriptor sb = info.SBuffers[index]; BufferDescriptor sb = info.SBuffers[index];
@ -177,9 +175,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags); _channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags);
} }
if ((_channel.BufferManager.HasUnalignedStorageBuffers) != hasUnaligned) if (_channel.BufferManager.HasUnalignedStorageBuffers != computeState.HasUnalignedStorageBuffer)
{ {
// Refetch the shader, as assumptions about storage buffer alignment have changed. // Refetch the shader, as assumptions about storage buffer alignment have changed.
computeState = new GpuChannelComputeState(
qmd.CtaThreadDimension0,
qmd.CtaThreadDimension1,
qmd.CtaThreadDimension2,
localMemorySize,
sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers);
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa); cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram); _context.Renderer.Pipeline.SetProgram(cs.HostProgram);
@ -187,30 +193,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
info = cs.Shaders[0].Info; info = cs.Shaders[0].Info;
} }
for (int index = 0; index < info.CBuffers.Count; index++)
{
BufferDescriptor cb = info.CBuffers[index];
// NVN uses the "hardware" constant buffer for anything that is less than 8,
// and those are already bound above.
// Anything greater than or equal to 8 uses the emulated constant buffers.
// They are emulated using global memory loads.
if (cb.Slot < 8)
{
continue;
}
ulong cbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(0);
int cbDescOffset = 0x260 + (cb.Slot - 8) * 0x10;
cbDescAddress += (ulong)cbDescOffset;
SbDescriptor cbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(cbDescAddress);
_channel.BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size);
}
_channel.BufferManager.SetComputeBufferBindings(cs.Bindings); _channel.BufferManager.SetComputeBufferBindings(cs.Bindings);
_channel.TextureManager.SetComputeBindings(cs.Bindings); _channel.TextureManager.SetComputeBindings(cs.Bindings);

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

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

View File

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

View File

@ -222,7 +222,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="gpuVa">Start GPU virtual address of the buffer</param> /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
private void RecordStorageAlignment(BuffersPerStage buffers, int index, ulong gpuVa) private void RecordStorageAlignment(BuffersPerStage buffers, int index, ulong gpuVa)
{ {
bool unaligned = (gpuVa & (Constants.StorageAlignment - 1)) != 0; bool unaligned = (gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1)) != 0;
if (unaligned || HasUnalignedStorageBuffers) if (unaligned || HasUnalignedStorageBuffers)
{ {

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 = 5044;
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;
} }
} }
@ -157,6 +141,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot; public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
public bool QueryHostSupportsShaderBarrierDivergence() => _context.Capabilities.SupportsShaderBarrierDivergence;
public bool QueryHostSupportsShaderFloat64() => _context.Capabilities.SupportsShaderFloat64;
public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat; public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat;
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod; public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
@ -165,6 +153,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

@ -92,7 +92,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
int imageBinding = stageIndex * imagesPerStage * 2; int imageBinding = stageIndex * imagesPerStage * 2;
AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage); AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
AddArrayDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage); AddDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage); AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage); AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
@ -133,19 +133,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
AddDescriptor(stages, type2, setIndex, binding + count, count); AddDescriptor(stages, type2, setIndex, binding + count, count);
} }
/// <summary>
/// Adds an array resource to the list of descriptors.
/// </summary>
/// <param name="stages">Shader stages where the resource is used</param>
/// <param name="type">Type of the resource</param>
/// <param name="setIndex">Descriptor set number where the resource will be bound</param>
/// <param name="binding">Binding number where the resource will be bound</param>
/// <param name="count">Number of resources bound at the binding location</param>
private void AddArrayDescriptor(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
{
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, count, type, stages));
}
/// <summary> /// <summary>
/// Adds buffer usage information to the list of usages. /// Adds buffer usage information to the list of usages.
/// </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

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

@ -104,14 +104,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
} }
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values); DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
var sBufferDescriptors = context.Config.GetStorageBufferDescriptors();
if (sBufferDescriptors.Length != 0)
{
DeclareStorages(context, sBufferDescriptors);
context.AppendLine();
}
var textureDescriptors = context.Config.GetTextureDescriptors(); var textureDescriptors = context.Config.GetTextureDescriptors();
if (textureDescriptors.Length != 0) if (textureDescriptors.Length != 0)
@ -239,33 +232,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)
@ -273,11 +243,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/AtomicMinMaxS32Shared.glsl"); AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/AtomicMinMaxS32Shared.glsl");
} }
if ((info.HelperFunctionsMask & HelperFunctionsMask.AtomicMinMaxS32Storage) != 0)
{
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/AtomicMinMaxS32Storage.glsl");
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0) if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0)
{ {
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl"); AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl");
@ -313,11 +278,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/StoreSharedSmallInt.glsl"); AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/StoreSharedSmallInt.glsl");
} }
if ((info.HelperFunctionsMask & HelperFunctionsMask.StoreStorageSmallInt) != 0)
{
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/StoreStorageSmallInt.glsl");
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0) if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0)
{ {
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl"); AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl");
@ -379,6 +339,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
} }
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers) private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
{
DeclareBuffers(context, buffers, "uniform");
}
private static void DeclareStorageBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
{
DeclareBuffers(context, buffers, "buffer");
}
private static void DeclareBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers, string declType)
{ {
foreach (BufferDefinition buffer in buffers) foreach (BufferDefinition buffer in buffers)
{ {
@ -388,7 +358,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
_ => "std430" _ => "std430"
}; };
context.AppendLine($"layout (binding = {buffer.Binding}, {layout}) uniform _{buffer.Name}"); context.AppendLine($"layout (binding = {buffer.Binding}, {layout}) {declType} _{buffer.Name}");
context.EnterScope(); context.EnterScope();
foreach (StructureField field in buffer.Type.Fields) foreach (StructureField field in buffer.Type.Fields)
@ -396,9 +366,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
if (field.Type.HasFlag(AggregateType.Array)) if (field.Type.HasFlag(AggregateType.Array))
{ {
string typeName = GetVarTypeName(context, field.Type & ~AggregateType.Array); string typeName = GetVarTypeName(context, field.Type & ~AggregateType.Array);
string arraySize = field.ArrayLength.ToString(CultureInfo.InvariantCulture);
context.AppendLine($"{typeName} {field.Name}[{arraySize}];"); if (field.ArrayLength > 0)
{
string arraySize = field.ArrayLength.ToString(CultureInfo.InvariantCulture);
context.AppendLine($"{typeName} {field.Name}[{arraySize}];");
}
else
{
context.AppendLine($"{typeName} {field.Name}[];");
}
} }
else else
{ {
@ -413,22 +391,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
} }
} }
private static void DeclareStorages(CodeGenContext context, BufferDescriptor[] descriptors)
{
string sbName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
sbName += "_" + DefaultNames.StorageNamePrefix;
string blockName = $"{sbName}_{DefaultNames.BlockSuffix}";
string layout = context.Config.Options.TargetApi == TargetApi.Vulkan ? ", set = 1" : string.Empty;
context.AppendLine($"layout (binding = {context.Config.FirstStorageBufferBinding}{layout}, std430) buffer {blockName}");
context.EnterScope();
context.AppendLine("uint " + DefaultNames.DataName + "[];");
context.LeaveScope($" {sbName}[{NumberFormatter.FormatInt(descriptors.Max(x => x.Slot) + 1)}];");
}
private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors) private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors)
{ {
int arraySize = 0; int arraySize = 0;
@ -756,7 +718,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
code = code.Replace("\t", CodeGenContext.Tab); code = code.Replace("\t", CodeGenContext.Tab);
code = code.Replace("$SHARED_MEM$", DefaultNames.SharedMemoryName); code = code.Replace("$SHARED_MEM$", DefaultNames.SharedMemoryName);
code = code.Replace("$STORAGE_MEM$", OperandManager.GetShaderStagePrefix(context.Config.Stage) + "_" + DefaultNames.StorageNamePrefix);
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot()) if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
{ {

View File

@ -11,12 +11,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public const string IAttributePrefix = "in_attr"; public const string IAttributePrefix = "in_attr";
public const string OAttributePrefix = "out_attr"; public const string OAttributePrefix = "out_attr";
public const string StorageNamePrefix = "s";
public const string DataName = "data";
public const string BlockSuffix = "block";
public const string LocalMemoryName = "local_mem"; public const string LocalMemoryName = "local_mem";
public const string SharedMemoryName = "shared_mem"; public const string SharedMemoryName = "shared_mem";

View File

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

View File

@ -1,21 +0,0 @@
int Helper_AtomicMaxS32(int index, int offset, int value)
{
uint oldValue, newValue;
do
{
oldValue = $STORAGE_MEM$[index].data[offset];
newValue = uint(max(int(oldValue), value));
} while (atomicCompSwap($STORAGE_MEM$[index].data[offset], oldValue, newValue) != oldValue);
return int(oldValue);
}
int Helper_AtomicMinS32(int index, int offset, int value)
{
uint oldValue, newValue;
do
{
oldValue = $STORAGE_MEM$[index].data[offset];
newValue = uint(min(int(oldValue), value));
} while (atomicCompSwap($STORAGE_MEM$[index].data[offset], oldValue, newValue) != oldValue);
return int(oldValue);
}

View File

@ -1,23 +0,0 @@
void Helper_StoreStorage16(int index, int offset, uint value)
{
int wordOffset = offset >> 2;
int bitOffset = (offset & 3) * 8;
uint oldValue, newValue;
do
{
oldValue = $STORAGE_MEM$[index].data[wordOffset];
newValue = bitfieldInsert(oldValue, value, bitOffset, 16);
} while (atomicCompSwap($STORAGE_MEM$[index].data[wordOffset], oldValue, newValue) != oldValue);
}
void Helper_StoreStorage8(int index, int offset, uint value)
{
int wordOffset = offset >> 2;
int bitOffset = (offset & 3) * 8;
uint oldValue, newValue;
do
{
oldValue = $STORAGE_MEM$[index].data[wordOffset];
newValue = bitfieldInsert(oldValue, value, bitOffset, 8);
} while (atomicCompSwap($STORAGE_MEM$[index].data[wordOffset], oldValue, newValue) != oldValue);
}

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

@ -68,33 +68,45 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
string args = string.Empty; string args = string.Empty;
for (int argIndex = 0; argIndex < arity; argIndex++) if (atomic && operation.StorageKind == StorageKind.StorageBuffer)
{ {
args = GenerateLoadOrStore(context, operation, isStore: false);
AggregateType dstType = operation.Inst == Instruction.AtomicMaxS32 || operation.Inst == Instruction.AtomicMinS32
? AggregateType.S32
: AggregateType.U32;
for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++)
{
args += ", " + GetSoureExpr(context, operation.GetSource(argIndex), dstType);
}
}
else if (atomic && operation.StorageKind == StorageKind.SharedMemory)
{
args = LoadShared(context, operation);
// For shared memory access, the second argument is unused and should be ignored. // For shared memory access, the second argument is unused and should be ignored.
// It is there to make both storage and shared access have the same number of arguments. // It is there to make both storage and shared access have the same number of arguments.
// For storage, both inputs are consumed when the argument index is 0, so we should skip it here. // For storage, both inputs are consumed when the argument index is 0, so we should skip it here.
if (argIndex == 1 && (atomic || operation.StorageKind == StorageKind.SharedMemory))
{
continue;
}
if (argIndex != 0) for (int argIndex = 2; argIndex < arity; argIndex++)
{ {
args += ", "; args += ", ";
}
if (argIndex == 0 && atomic) AggregateType dstType = GetSrcVarType(inst, argIndex);
args += GetSoureExpr(context, operation.GetSource(argIndex), dstType);
}
}
else
{
for (int argIndex = 0; argIndex < arity; argIndex++)
{ {
switch (operation.StorageKind) if (argIndex != 0)
{ {
case StorageKind.SharedMemory: args += LoadShared(context, operation); break; args += ", ";
case StorageKind.StorageBuffer: args += LoadStorage(context, operation); break;
default: throw new InvalidOperationException($"Invalid storage kind \"{operation.StorageKind}\".");
} }
}
else
{
AggregateType dstType = GetSrcVarType(inst, argIndex); AggregateType dstType = GetSrcVarType(inst, argIndex);
args += GetSoureExpr(context, operation.GetSource(argIndex), dstType); args += GetSoureExpr(context, operation.GetSource(argIndex), dstType);
@ -173,9 +185,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
case Instruction.LoadShared: case Instruction.LoadShared:
return LoadShared(context, operation); return LoadShared(context, operation);
case Instruction.LoadStorage:
return LoadStorage(context, operation);
case Instruction.Lod: case Instruction.Lod:
return Lod(context, operation); return Lod(context, operation);
@ -203,15 +212,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
case Instruction.StoreShared8: case Instruction.StoreShared8:
return StoreShared8(context, operation); return StoreShared8(context, operation);
case Instruction.StoreStorage:
return StoreStorage(context, operation);
case Instruction.StoreStorage16:
return StoreStorage16(context, operation);
case Instruction.StoreStorage8:
return StoreStorage8(context, operation);
case Instruction.TextureSample: case Instruction.TextureSample:
return TextureSample(context, operation); return TextureSample(context, operation);

View File

@ -85,7 +85,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.Load, InstType.Special); Add(Instruction.Load, InstType.Special);
Add(Instruction.LoadLocal, InstType.Special); Add(Instruction.LoadLocal, InstType.Special);
Add(Instruction.LoadShared, InstType.Special); Add(Instruction.LoadShared, InstType.Special);
Add(Instruction.LoadStorage, InstType.Special);
Add(Instruction.Lod, InstType.Special); Add(Instruction.Lod, InstType.Special);
Add(Instruction.LogarithmB2, InstType.CallUnary, "log2"); Add(Instruction.LogarithmB2, InstType.CallUnary, "log2");
Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9); Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9);
@ -101,6 +100,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);
@ -122,9 +122,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.StoreShared, InstType.Special); Add(Instruction.StoreShared, InstType.Special);
Add(Instruction.StoreShared16, InstType.Special); Add(Instruction.StoreShared16, InstType.Special);
Add(Instruction.StoreShared8, InstType.Special); Add(Instruction.StoreShared8, InstType.Special);
Add(Instruction.StoreStorage, InstType.Special);
Add(Instruction.StoreStorage16, InstType.Special);
Add(Instruction.StoreStorage8, InstType.Special);
Add(Instruction.Subtract, InstType.OpBinary, "-", 2); Add(Instruction.Subtract, InstType.OpBinary, "-", 2);
Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd); Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd);
Add(Instruction.TextureSample, InstType.Special); Add(Instruction.TextureSample, InstType.Special);

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
{ {
@ -234,17 +210,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return $"{arrayName}[{offsetExpr}]"; return $"{arrayName}[{offsetExpr}]";
} }
public static string LoadStorage(CodeGenContext context, AstOperation operation)
{
IAstNode src1 = operation.GetSource(0);
IAstNode src2 = operation.GetSource(1);
string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
return GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
}
public static string Lod(CodeGenContext context, AstOperation operation) public static string Lod(CodeGenContext context, AstOperation operation)
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
@ -350,60 +315,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return $"{HelperFunctionNames.StoreShared8}({offsetExpr}, {src})"; return $"{HelperFunctionNames.StoreShared8}({offsetExpr}, {src})";
} }
public static string StoreStorage(CodeGenContext context, AstOperation operation)
{
IAstNode src1 = operation.GetSource(0);
IAstNode src2 = operation.GetSource(1);
IAstNode src3 = operation.GetSource(2);
string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
AggregateType srcType = OperandManager.GetNodeDestType(context, src3);
string src = TypeConversion.ReinterpretCast(context, src3, srcType, AggregateType.U32);
string sb = GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
return $"{sb} = {src}";
}
public static string StoreStorage16(CodeGenContext context, AstOperation operation)
{
IAstNode src1 = operation.GetSource(0);
IAstNode src2 = operation.GetSource(1);
IAstNode src3 = operation.GetSource(2);
string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
AggregateType srcType = OperandManager.GetNodeDestType(context, src3);
string src = TypeConversion.ReinterpretCast(context, src3, srcType, AggregateType.U32);
string sb = GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
return $"{HelperFunctionNames.StoreStorage16}({indexExpr}, {offsetExpr}, {src})";
}
public static string StoreStorage8(CodeGenContext context, AstOperation operation)
{
IAstNode src1 = operation.GetSource(0);
IAstNode src2 = operation.GetSource(1);
IAstNode src3 = operation.GetSource(2);
string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
AggregateType srcType = OperandManager.GetNodeDestType(context, src3);
string src = TypeConversion.ReinterpretCast(context, src3, srcType, AggregateType.U32);
string sb = GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
return $"{HelperFunctionNames.StoreStorage8}({indexExpr}, {offsetExpr}, {src})";
}
public static string TextureSample(CodeGenContext context, AstOperation operation) public static string TextureSample(CodeGenContext context, AstOperation operation)
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
@ -584,53 +495,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 +615,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,37 +632,38 @@ 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;
} }
} }
private static string GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore) public static string GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
{ {
StorageKind storageKind = operation.StorageKind; StorageKind storageKind = operation.StorageKind;
string varName; string varName;
AggregateType varType; AggregateType varType;
int srcIndex = 0; int srcIndex = 0;
int inputsCount = isStore ? operation.SourcesCount - 1 : operation.SourcesCount; bool isStoreOrAtomic = operation.Inst == Instruction.Store || operation.Inst.IsAtomic();
int inputsCount = isStoreOrAtomic ? operation.SourcesCount - 1 : operation.SourcesCount;
if (operation.Inst == Instruction.AtomicCompareAndSwap)
{
inputsCount--;
}
switch (storageKind) switch (storageKind)
{ {
case StorageKind.ConstantBuffer: case StorageKind.ConstantBuffer:
case StorageKind.StorageBuffer:
if (!(operation.GetSource(srcIndex++) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant) if (!(operation.GetSource(srcIndex++) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
{ {
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand."); throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
} }
int binding = bindingIndex.Value; int binding = bindingIndex.Value;
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[binding]; BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
? context.Config.Properties.ConstantBuffers[binding]
: context.Config.Properties.StorageBuffers[binding];
if (!(operation.GetSource(srcIndex++) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant) if (!(operation.GetSource(srcIndex++) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
{ {
@ -903,15 +769,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return varName; return varName;
} }
private static string GetStorageBufferAccessor(string slotExpr, string offsetExpr, ShaderStage stage)
{
string sbName = OperandManager.GetShaderStagePrefix(stage);
sbName += "_" + DefaultNames.StorageNamePrefix;
return $"{sbName}[{slotExpr}].{DefaultNames.DataName}[{offsetExpr}]";
}
private static string GetMask(int index) private static string GetMask(int index)
{ {
return $".{"rgba".AsSpan(index, 1)}"; return $".{"rgba".AsSpan(index, 1)}";

View File

@ -118,6 +118,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
switch (operation.StorageKind) switch (operation.StorageKind)
{ {
case StorageKind.ConstantBuffer: case StorageKind.ConstantBuffer:
case StorageKind.StorageBuffer:
if (!(operation.GetSource(0) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant) if (!(operation.GetSource(0) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
{ {
throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand."); throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
@ -128,7 +129,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
throw new InvalidOperationException($"Second input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand."); throw new InvalidOperationException($"Second input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
} }
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[bindingIndex.Value]; BufferDefinition buffer = operation.StorageKind == StorageKind.ConstantBuffer
? context.Config.Properties.ConstantBuffers[bindingIndex.Value]
: context.Config.Properties.StorageBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value]; StructureField field = buffer.Type.Fields[fieldIndex.Value];
return field.Type & AggregateType.ElementTypeMask; return field.Type & AggregateType.ElementTypeMask;

View File

@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public int InputVertices { get; } public int InputVertices { get; }
public Dictionary<int, Instruction> ConstantBuffers { get; } = new Dictionary<int, Instruction>(); public Dictionary<int, Instruction> ConstantBuffers { get; } = new Dictionary<int, Instruction>();
public Instruction StorageBuffersArray { get; set; } public Dictionary<int, Instruction> StorageBuffers { get; } = new Dictionary<int, Instruction>();
public Instruction LocalMemory { get; set; } public Instruction LocalMemory { get; set; }
public Instruction SharedMemory { get; set; } public Instruction SharedMemory { get; set; }
public Dictionary<TextureMeta, SamplerType> SamplersTypes { get; } = new Dictionary<TextureMeta, SamplerType>(); public Dictionary<TextureMeta, SamplerType> SamplersTypes { get; } = new Dictionary<TextureMeta, SamplerType>();
@ -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>();
@ -75,6 +76,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public SpirvDelegates Delegates { get; } public SpirvDelegates Delegates { get; }
public bool IsMainFunction { get; private set; }
public bool MayHaveReturned { get; set; }
public CodeGenContext( public CodeGenContext(
StructuredProgramInfo info, StructuredProgramInfo info,
ShaderConfig config, ShaderConfig config,
@ -107,8 +111,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Delegates = new SpirvDelegates(this); Delegates = new SpirvDelegates(this);
} }
public void StartFunction() public void StartFunction(bool isMainFunction)
{ {
IsMainFunction = isMainFunction;
MayHaveReturned = false;
_locals.Clear(); _locals.Clear();
_localForArgs.Clear(); _localForArgs.Clear();
_funcArgs.Clear(); _funcArgs.Clear();
@ -307,7 +313,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{ {
if ((type & AggregateType.Array) != 0) if ((type & AggregateType.Array) != 0)
{ {
return TypeArray(GetType(type & ~AggregateType.Array), Constant(TypeU32(), length)); if (length > 0)
{
return TypeArray(GetType(type & ~AggregateType.Array), Constant(TypeU32(), length));
}
else
{
return TypeRuntimeArray(GetType(type & ~AggregateType.Array));
}
} }
else if ((type & AggregateType.ElementCountMask) != 0) else if ((type & AggregateType.ElementCountMask) != 0)
{ {

View File

@ -5,6 +5,7 @@ using Ryujinx.Graphics.Shader.Translation;
using Spv.Generator; using Spv.Generator;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using static Spv.Specification; using static Spv.Specification;
@ -99,7 +100,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values); DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Config.GetStorageBufferDescriptors()); DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
DeclareSamplers(context, context.Config.GetTextureDescriptors()); DeclareSamplers(context, context.Config.GetTextureDescriptors());
DeclareImages(context, context.Config.GetImageDescriptors()); DeclareImages(context, context.Config.GetImageDescriptors());
DeclareInputsAndOutputs(context, info); DeclareInputsAndOutputs(context, info);
@ -127,6 +128,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers) private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
{
DeclareBuffers(context, buffers, isBuffer: false);
}
private static void DeclareStorageBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
{
DeclareBuffers(context, buffers, isBuffer: true);
}
private static void DeclareBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers, bool isBuffer)
{ {
HashSet<SpvInstruction> decoratedTypes = new HashSet<SpvInstruction>(); HashSet<SpvInstruction> decoratedTypes = new HashSet<SpvInstruction>();
@ -155,6 +166,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.Decorate(structFieldTypes[fieldIndex], Decoration.ArrayStride, (LiteralInteger)fieldSize); context.Decorate(structFieldTypes[fieldIndex], Decoration.ArrayStride, (LiteralInteger)fieldSize);
} }
// Zero lengths are assumed to be a "runtime array" (which does not have a explicit length
// specified on the shader, and instead assumes the bound buffer length).
// It is only valid as the last struct element.
Debug.Assert(field.ArrayLength > 0 || fieldIndex == buffer.Type.Fields.Length - 1);
offset += fieldSize * field.ArrayLength; offset += fieldSize * field.ArrayLength;
} }
else else
@ -163,56 +180,37 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
} }
var ubStructType = context.TypeStruct(false, structFieldTypes); var structType = context.TypeStruct(false, structFieldTypes);
if (decoratedTypes.Add(ubStructType)) if (decoratedTypes.Add(structType))
{ {
context.Decorate(ubStructType, Decoration.Block); context.Decorate(structType, isBuffer ? Decoration.BufferBlock : Decoration.Block);
for (int fieldIndex = 0; fieldIndex < structFieldOffsets.Length; fieldIndex++) for (int fieldIndex = 0; fieldIndex < structFieldOffsets.Length; fieldIndex++)
{ {
context.MemberDecorate(ubStructType, fieldIndex, Decoration.Offset, (LiteralInteger)structFieldOffsets[fieldIndex]); context.MemberDecorate(structType, fieldIndex, Decoration.Offset, (LiteralInteger)structFieldOffsets[fieldIndex]);
} }
} }
var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructType); var pointerType = context.TypePointer(StorageClass.Uniform, structType);
var ubVariable = context.Variable(ubPointerType, StorageClass.Uniform); var variable = context.Variable(pointerType, StorageClass.Uniform);
context.Name(ubVariable, buffer.Name); context.Name(variable, buffer.Name);
context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)buffer.Set); context.Decorate(variable, Decoration.DescriptorSet, (LiteralInteger)buffer.Set);
context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)buffer.Binding); context.Decorate(variable, Decoration.Binding, (LiteralInteger)buffer.Binding);
context.AddGlobalVariable(ubVariable); context.AddGlobalVariable(variable);
context.ConstantBuffers.Add(buffer.Binding, ubVariable);
if (isBuffer)
{
context.StorageBuffers.Add(buffer.Binding, variable);
}
else
{
context.ConstantBuffers.Add(buffer.Binding, variable);
}
} }
} }
private static void DeclareStorageBuffers(CodeGenContext context, BufferDescriptor[] descriptors)
{
if (descriptors.Length == 0)
{
return;
}
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? 1 : 0;
int count = descriptors.Max(x => x.Slot) + 1;
var sbArrayType = context.TypeRuntimeArray(context.TypeU32());
context.Decorate(sbArrayType, Decoration.ArrayStride, (LiteralInteger)4);
var sbStructType = context.TypeStruct(true, sbArrayType);
context.Decorate(sbStructType, Decoration.BufferBlock);
context.MemberDecorate(sbStructType, 0, Decoration.Offset, (LiteralInteger)0);
var sbStructArrayType = context.TypeArray(sbStructType, context.Constant(context.TypeU32(), count));
var sbPointerType = context.TypePointer(StorageClass.Uniform, sbStructArrayType);
var sbVariable = context.Variable(sbPointerType, StorageClass.Uniform);
context.Name(sbVariable, $"{GetStagePrefix(context.Config.Stage)}_s");
context.Decorate(sbVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
context.Decorate(sbVariable, Decoration.Binding, (LiteralInteger)context.Config.FirstStorageBufferBinding);
context.AddGlobalVariable(sbVariable);
context.StorageBuffersArray = sbVariable;
}
private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors) private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors)
{ {
foreach (var descriptor in descriptors) foreach (var descriptor in descriptors)

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;
@ -100,7 +99,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.Load, GenerateLoad); Add(Instruction.Load, GenerateLoad);
Add(Instruction.LoadLocal, GenerateLoadLocal); Add(Instruction.LoadLocal, GenerateLoadLocal);
Add(Instruction.LoadShared, GenerateLoadShared); Add(Instruction.LoadShared, GenerateLoadShared);
Add(Instruction.LoadStorage, GenerateLoadStorage);
Add(Instruction.Lod, GenerateLod); Add(Instruction.Lod, GenerateLod);
Add(Instruction.LogarithmB2, GenerateLogarithmB2); Add(Instruction.LogarithmB2, GenerateLogarithmB2);
Add(Instruction.LogicalAnd, GenerateLogicalAnd); Add(Instruction.LogicalAnd, GenerateLogicalAnd);
@ -114,6 +112,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);
@ -137,9 +136,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.StoreShared, GenerateStoreShared); Add(Instruction.StoreShared, GenerateStoreShared);
Add(Instruction.StoreShared16, GenerateStoreShared16); Add(Instruction.StoreShared16, GenerateStoreShared16);
Add(Instruction.StoreShared8, GenerateStoreShared8); Add(Instruction.StoreShared8, GenerateStoreShared8);
Add(Instruction.StoreStorage, GenerateStoreStorage);
Add(Instruction.StoreStorage16, GenerateStoreStorage16);
Add(Instruction.StoreStorage8, GenerateStoreStorage8);
Add(Instruction.Subtract, GenerateSubtract); Add(Instruction.Subtract, GenerateSubtract);
Add(Instruction.SwizzleAdd, GenerateSwizzleAdd); Add(Instruction.SwizzleAdd, GenerateSwizzleAdd);
Add(Instruction.TextureSample, GenerateTextureSample); Add(Instruction.TextureSample, GenerateTextureSample);
@ -246,6 +242,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateBarrier(CodeGenContext context, AstOperation operation) private static OperationResult GenerateBarrier(CodeGenContext context, AstOperation operation)
{ {
// Barrier on divergent control flow paths may cause the GPU to hang,
// so skip emitting the barrier for those cases.
if (!context.Config.GpuAccessor.QueryHostSupportsShaderBarrierDivergence() &&
(context.CurrentBlock.Type != AstBlockType.Main || context.MayHaveReturned || !context.IsMainFunction))
{
context.Config.GpuAccessor.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
return OperationResult.Invalid;
}
context.ControlBarrier( context.ControlBarrier(
context.Constant(context.TypeU32(), Scope.Workgroup), context.Constant(context.TypeU32(), Scope.Workgroup),
context.Constant(context.TypeU32(), Scope.Workgroup), context.Constant(context.TypeU32(), Scope.Workgroup),
@ -744,8 +750,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);
@ -891,14 +895,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return new OperationResult(AggregateType.U32, value); return new OperationResult(AggregateType.U32, value);
} }
private static OperationResult GenerateLoadStorage(CodeGenContext context, AstOperation operation)
{
var elemPointer = GetStorageElemPointer(context, operation);
var value = context.Load(context.TypeU32(), elemPointer);
return new OperationResult(AggregateType.U32, value);
}
private static OperationResult GenerateLod(CodeGenContext context, AstOperation operation) private static OperationResult GenerateLod(CodeGenContext context, AstOperation operation)
{ {
AstTextureOperation texOp = (AstTextureOperation)operation; AstTextureOperation texOp = (AstTextureOperation)operation;
@ -1040,6 +1036,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 +1102,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateReturn(CodeGenContext context, AstOperation operation) private static OperationResult GenerateReturn(CodeGenContext context, AstOperation operation)
{ {
context.Return(); context.MayHaveReturned = true;
if (operation.SourcesCount != 0)
{
context.ReturnValue(context.Get(context.CurrentFunction.ReturnType, operation.GetSource(0)));
}
else
{
context.Return();
}
return OperationResult.Invalid; return OperationResult.Invalid;
} }
@ -1296,28 +1307,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return OperationResult.Invalid; return OperationResult.Invalid;
} }
private static OperationResult GenerateStoreStorage(CodeGenContext context, AstOperation operation)
{
var elemPointer = GetStorageElemPointer(context, operation);
context.Store(elemPointer, context.Get(AggregateType.U32, operation.GetSource(2)));
return OperationResult.Invalid;
}
private static OperationResult GenerateStoreStorage16(CodeGenContext context, AstOperation operation)
{
GenerateStoreStorageSmallInt(context, operation, 16);
return OperationResult.Invalid;
}
private static OperationResult GenerateStoreStorage8(CodeGenContext context, AstOperation operation)
{
GenerateStoreStorageSmallInt(context, operation, 8);
return OperationResult.Invalid;
}
private static OperationResult GenerateSubtract(CodeGenContext context, AstOperation operation) private static OperationResult GenerateSubtract(CodeGenContext context, AstOperation operation)
{ {
return GenerateBinary(context, operation, context.Delegates.FSub, context.Delegates.ISub); return GenerateBinary(context, operation, context.Delegates.FSub, context.Delegates.ISub);
@ -1439,35 +1428,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 +1599,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 +1714,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);
} }
} }
@ -1873,13 +1827,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
AstOperation operation, AstOperation operation,
Func<SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction> emitU) Func<SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction> emitU)
{ {
var value = context.GetU32(operation.GetSource(2)); var value = context.GetU32(operation.GetSource(operation.SourcesCount - 1));
SpvInstruction elemPointer; SpvInstruction elemPointer;
if (operation.StorageKind == StorageKind.StorageBuffer) if (operation.StorageKind == StorageKind.StorageBuffer)
{ {
elemPointer = GetStorageElemPointer(context, operation); elemPointer = GetStoragePointer(context, operation, out _);
} }
else if (operation.StorageKind == StorageKind.SharedMemory) else if (operation.StorageKind == StorageKind.SharedMemory)
{ {
@ -1899,14 +1853,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateAtomicMemoryCas(CodeGenContext context, AstOperation operation) private static OperationResult GenerateAtomicMemoryCas(CodeGenContext context, AstOperation operation)
{ {
var value0 = context.GetU32(operation.GetSource(2)); var value0 = context.GetU32(operation.GetSource(operation.SourcesCount - 2));
var value1 = context.GetU32(operation.GetSource(3)); var value1 = context.GetU32(operation.GetSource(operation.SourcesCount - 1));
SpvInstruction elemPointer; SpvInstruction elemPointer;
if (operation.StorageKind == StorageKind.StorageBuffer) if (operation.StorageKind == StorageKind.StorageBuffer)
{ {
elemPointer = GetStorageElemPointer(context, operation); elemPointer = GetStoragePointer(context, operation, out _);
} }
else if (operation.StorageKind == StorageKind.SharedMemory) else if (operation.StorageKind == StorageKind.SharedMemory)
{ {
@ -1925,17 +1879,33 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
private static OperationResult GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore) private static OperationResult GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
{
SpvInstruction pointer = GetStoragePointer(context, operation, out AggregateType varType);
if (isStore)
{
context.Store(pointer, context.Get(varType, operation.GetSource(operation.SourcesCount - 1)));
return OperationResult.Invalid;
}
else
{
var result = context.Load(context.GetType(varType), pointer);
return new OperationResult(varType, result);
}
}
private static SpvInstruction GetStoragePointer(CodeGenContext context, AstOperation operation, out AggregateType varType)
{ {
StorageKind storageKind = operation.StorageKind; StorageKind storageKind = operation.StorageKind;
StorageClass storageClass; StorageClass storageClass;
SpvInstruction baseObj; SpvInstruction baseObj;
AggregateType varType;
int srcIndex = 0; int srcIndex = 0;
switch (storageKind) switch (storageKind)
{ {
case StorageKind.ConstantBuffer: case StorageKind.ConstantBuffer:
case StorageKind.StorageBuffer:
if (!(operation.GetSource(srcIndex++) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant) if (!(operation.GetSource(srcIndex++) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
{ {
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand."); throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
@ -1946,12 +1916,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand."); throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
} }
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[bindingIndex.Value]; BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
? context.Config.Properties.ConstantBuffers[bindingIndex.Value]
: context.Config.Properties.StorageBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value]; StructureField field = buffer.Type.Fields[fieldIndex.Value];
storageClass = StorageClass.Uniform; storageClass = StorageClass.Uniform;
varType = field.Type & AggregateType.ElementTypeMask; varType = field.Type & AggregateType.ElementTypeMask;
baseObj = context.ConstantBuffers[bindingIndex.Value]; baseObj = storageKind == StorageKind.ConstantBuffer
? context.ConstantBuffers[bindingIndex.Value]
: context.StorageBuffers[bindingIndex.Value];
break; break;
case StorageKind.Input: case StorageKind.Input:
@ -2017,7 +1991,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
throw new InvalidOperationException($"Invalid storage kind {storageKind}."); throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
} }
int inputsCount = (isStore ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex; bool isStoreOrAtomic = operation.Inst == Instruction.Store || operation.Inst.IsAtomic();
int inputsCount = (isStoreOrAtomic ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
if (operation.Inst == Instruction.AtomicCompareAndSwap)
{
inputsCount--;
}
SpvInstruction e0, e1, e2; SpvInstruction e0, e1, e2;
SpvInstruction pointer; SpvInstruction pointer;
@ -2054,16 +2035,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
break; break;
} }
if (isStore) return pointer;
{
context.Store(pointer, context.Get(varType, operation.GetSource(srcIndex)));
return OperationResult.Invalid;
}
else
{
var result = context.Load(context.GetType(varType), pointer);
return new OperationResult(varType, result);
}
} }
private static SpvInstruction GetScalarInput(CodeGenContext context, IoVariable ioVariable) private static SpvInstruction GetScalarInput(CodeGenContext context, IoVariable ioVariable)
@ -2092,25 +2064,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
GenerateStoreSmallInt(context, elemPointer, bitOffset, value, bitSize); GenerateStoreSmallInt(context, elemPointer, bitOffset, value, bitSize);
} }
private static void GenerateStoreStorageSmallInt(CodeGenContext context, AstOperation operation, int bitSize)
{
var i0 = context.Get(AggregateType.S32, operation.GetSource(0));
var offset = context.Get(AggregateType.U32, operation.GetSource(1));
var value = context.Get(AggregateType.U32, operation.GetSource(2));
var wordOffset = context.ShiftRightLogical(context.TypeU32(), offset, context.Constant(context.TypeU32(), 2));
var bitOffset = context.BitwiseAnd(context.TypeU32(), offset, context.Constant(context.TypeU32(), 3));
bitOffset = context.ShiftLeftLogical(context.TypeU32(), bitOffset, context.Constant(context.TypeU32(), 3));
var sbVariable = context.StorageBuffersArray;
var i1 = context.Constant(context.TypeS32(), 0);
var elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeU32()), sbVariable, i0, i1, wordOffset);
GenerateStoreSmallInt(context, elemPointer, bitOffset, value, bitSize);
}
private static void GenerateStoreSmallInt( private static void GenerateStoreSmallInt(
CodeGenContext context, CodeGenContext context,
SpvInstruction elemPointer, SpvInstruction elemPointer,
@ -2197,16 +2150,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
} }
} }
private static SpvInstruction GetStorageElemPointer(CodeGenContext context, AstOperation operation)
{
var sbVariable = context.StorageBuffersArray;
var i0 = context.Get(AggregateType.S32, operation.GetSource(0));
var i1 = context.Constant(context.TypeS32(), 0);
var i2 = context.Get(AggregateType.S32, operation.GetSource(1));
return context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeU32()), sbVariable, i0, i1, i2);
}
private static OperationResult GenerateUnary( private static OperationResult GenerateUnary(
CodeGenContext context, CodeGenContext context,
AstOperation operation, AstOperation operation,
@ -2269,7 +2212,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 +2223,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 +2283,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 +2294,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,12 +144,11 @@ 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(isMainFunction: funcIndex == 0);
Declarations.DeclareParameters(context, function); Declarations.DeclareParameters(context, function);

View File

@ -10,7 +10,5 @@ namespace Ryujinx.Graphics.Shader
public const int NvnBaseVertexByteOffset = 0x640; public const int NvnBaseVertexByteOffset = 0x640;
public const int NvnBaseInstanceByteOffset = 0x644; public const int NvnBaseInstanceByteOffset = 0x644;
public const int NvnDrawIndexByteOffset = 0x648; public const int NvnDrawIndexByteOffset = 0x648;
public const int StorageAlignment = 16;
} }
} }

View File

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

@ -164,6 +164,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.Ccc == Ccc.T) if (op.Ccc == Ccc.T)
{ {
context.PrepareForReturn();
context.Return(); context.Return();
} }
else else
@ -175,6 +176,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{ {
Operand lblSkip = Label(); Operand lblSkip = Label();
context.BranchIfFalse(lblSkip, cond); context.BranchIfFalse(lblSkip, cond);
context.PrepareForReturn();
context.Return(); context.Return();
context.MarkLabel(lblSkip); context.MarkLabel(lblSkip);
} }

View File

@ -336,13 +336,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
int offset, int offset,
bool extended) bool extended)
{ {
bool isSmallInt = size < LsSize.B32;
int count = GetVectorCount(size); int count = GetVectorCount(size);
StorageKind storageKind = GetStorageKind(size);
(Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, new Register(ra, RegisterType.Gpr), extended, offset); (_, Operand addrHigh) = Get40BitsAddress(context, new Register(ra, RegisterType.Gpr), extended, offset);
Operand bitOffset = GetBitOffset(context, addrLow); Operand srcA = context.Copy(new Operand(new Register(ra, RegisterType.Gpr)));
for (int index = 0; index < count; index++) for (int index = 0; index < count; index++)
{ {
@ -353,12 +352,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
break; break;
} }
Operand value = context.LoadGlobal(context.IAdd(addrLow, Const(index * 4)), addrHigh); Operand value = context.Load(storageKind, context.IAdd(srcA, Const(offset + index * 4)), addrHigh);
if (isSmallInt)
{
value = ExtractSmallInt(context, size, bitOffset, value);
}
context.Copy(Register(dest), value); context.Copy(Register(dest), value);
} }
@ -445,10 +439,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
} }
int count = GetVectorCount((LsSize)size); int count = GetVectorCount((LsSize)size);
StorageKind storageKind = GetStorageKind((LsSize)size);
(Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, new Register(ra, RegisterType.Gpr), extended, offset); (_, Operand addrHigh) = Get40BitsAddress(context, new Register(ra, RegisterType.Gpr), extended, offset);
Operand bitOffset = GetBitOffset(context, addrLow); Operand srcA = context.Copy(new Operand(new Register(ra, RegisterType.Gpr)));
for (int index = 0; index < count; index++) for (int index = 0; index < count; index++)
{ {
@ -456,23 +451,24 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand value = Register(isRz ? rd : rd + index, RegisterType.Gpr); Operand value = Register(isRz ? rd : rd + index, RegisterType.Gpr);
Operand addrLowOffset = context.IAdd(addrLow, Const(index * 4)); Operand addrLowOffset = context.IAdd(srcA, Const(offset + index * 4));
if (size == LsSize2.U8 || size == LsSize2.S8) context.Store(storageKind, addrLowOffset, addrHigh, value);
{
context.StoreGlobal8(addrLowOffset, addrHigh, value);
}
else if (size == LsSize2.U16 || size == LsSize2.S16)
{
context.StoreGlobal16(addrLowOffset, addrHigh, value);
}
else
{
context.StoreGlobal(addrLowOffset, addrHigh, value);
}
} }
} }
private static StorageKind GetStorageKind(LsSize size)
{
return size switch
{
LsSize.U8 => StorageKind.GlobalMemoryU8,
LsSize.S8 => StorageKind.GlobalMemoryS8,
LsSize.U16 => StorageKind.GlobalMemoryU16,
LsSize.S16 => StorageKind.GlobalMemoryS16,
_ => StorageKind.GlobalMemory
};
}
private static int GetVectorCount(LsSize size) private static int GetVectorCount(LsSize size)
{ {
switch (size) switch (size)

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

@ -79,10 +79,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
ImageAtomic, ImageAtomic,
IsNan, IsNan,
Load, Load,
LoadGlobal,
LoadLocal, LoadLocal,
LoadShared, LoadShared,
LoadStorage,
Lod, Lod,
LogarithmB2, LogarithmB2,
LogicalAnd, LogicalAnd,
@ -97,6 +95,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
MemoryBarrier, MemoryBarrier,
Minimum, Minimum,
MinimumU32, MinimumU32,
Modulo,
Multiply, Multiply,
MultiplyHighS32, MultiplyHighS32,
MultiplyHighU32, MultiplyHighU32,
@ -116,16 +115,10 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Sine, Sine,
SquareRoot, SquareRoot,
Store, Store,
StoreGlobal,
StoreGlobal16,
StoreGlobal8,
StoreLocal, StoreLocal,
StoreShared, StoreShared,
StoreShared16, StoreShared16,
StoreShared8, StoreShared8,
StoreStorage,
StoreStorage16,
StoreStorage8,
Subtract, Subtract,
SwizzleAdd, SwizzleAdd,
TextureSample, TextureSample,

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

View File

@ -11,7 +11,12 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
StorageBuffer, StorageBuffer,
LocalMemory, LocalMemory,
SharedMemory, SharedMemory,
GlobalMemory GlobalMemory,
// TODO: Remove those and store type as a field on the Operation class itself.
GlobalMemoryS8,
GlobalMemoryS16,
GlobalMemoryU8,
GlobalMemoryU16
} }
static class StorageKindExtensions static class StorageKindExtensions

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" />
@ -15,7 +11,6 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\AtomicMinMaxS32Shared.glsl" /> <EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\AtomicMinMaxS32Shared.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\AtomicMinMaxS32Storage.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighS32.glsl" /> <EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighS32.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighU32.glsl" /> <EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighU32.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\Shuffle.glsl" /> <EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\Shuffle.glsl" />
@ -23,11 +18,7 @@
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleUp.glsl" /> <EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleUp.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleXor.glsl" /> <EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleXor.glsl" />
<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\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)
{ {
} }

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