Compare commits

...

63 Commits

Author SHA1 Message Date
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
ac66643346 Fix crash in SettingsViewModel when Vulkan isn't available (#4985)
* fix crash when Vulkan isn't available

* add VulkanRenderer.GetPhysicalDevices() overload that provides its own Vk API object and logs on failure

* adjustments per AcK77
2023-05-21 21:39:06 +02:00
21e88f17f6 ServerBase thread safety (#4577)
* Add guard against ServerBase.Dispose() being called multiple times. Add reset event to avoid Dispose() being called while the ServerLoop is still running.

* remove unused usings

* rework ServerBase to use one collection each for sessions and ports, and make all accesses thread-safe.

* fix Logger call

* use GetSessionObj(int) instead of using _sessions directly

* move _threadStopped check inside "dispose once" test

* - Replace _threadStopped event with attempt to Join() the ending thread (if that isn't the current thread) instead.

- Use the instance-local _selfProcess and (new) _selfThread variables to avoid suggesting that the current KProcess and KThread could change. Per gdkchan, they can't currently, and this old IPC system will be removed before that changes.

- Re-order Dispose() so that the Interlocked _isDisposed check is the last check before disposing, to increase the likelihood that multiple callers will result in one of them succeeding.

* code style suggestions per AcK77

* add infinite wait for thread termination
2023-05-21 21:28:51 +02:00
5626f2ca1c Replace ShaderBindings with new ResourceLayout structure for Vulkan (#5025)
* Introduce ResourceLayout

* Part 1: Use new ResourceSegments array on UpdateAndBind

* Part 2: Use ResourceLayout to build PipelineLayout

* Delete old code

* XML docs

* Fix shader cache load NRE

* Fix typo
2023-05-21 14:04:21 -03:00
402f05b8ef Replace constant buffer access on shader with new Load instruction (#4646) 2023-05-20 16:19:26 -03:00
fb27042e01 Limit compute storage buffer size (#5028) 2023-05-20 16:15:07 +00:00
69a9de33d3 SPIR-V: Only allow implicit LOD sampling on fragment (#5026) 2023-05-20 15:52:26 +02:00
bba51c2eeb Fix macOS Update Script (#5014)
* Update updater.sh

* Better script

* Revert "Better script"

This reverts commit 9bf6be863892e5e10c2f2dba45f1d0a60daca688.
2023-05-19 21:20:01 +02:00
fc26189fe1 Eliminate redundant multiplications by gl_FragCoord.w on the shader (#4578)
* Eliminate redundant multiplications by gl_FragCoord.w on the shader

* Shader cache version bump
2023-05-19 11:52:31 -03:00
a40c90e7dd nuget: bump DynamicData from 7.13.5 to 7.13.8 (#5001)
Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 7.13.5 to 7.13.8.
- [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.5...7.13.8)

---
updated-dependencies:
- dependency-name: DynamicData
  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-05-19 06:52:44 +02:00
f864a49014 Fix Vulkan blit-like operations swizzle (#5003) 2023-05-18 18:16:03 -03:00
ecbf303266 GPU: Avoid using garbage size for non-cb0 storage buffers (#4999)
* GPU: Avoid using garbage size for non-cb0 storage buffers

In the depths area, Tears of the Kingdom uses a global memory access with address on constant buffer slot 6. This isn't standard and thus doesn't actually have a size 8 bytes after it, so we were reading back a garbage size that ended up very large (at least in version 1.1.0), and would synchronize a lot of data per frame.

This PR makes storage buffers created from addresses outside constant buffer slot 0 get their size as the number of bytes remaining in the GPU mapping starting at the given virtual address. This should bound the buffer to a reasonable size, and ideally stop it crossing into other memory.

* Limit max size

* Add TODO

* Feedback
2023-05-18 08:56:34 +02:00
b3bf05356b ava: Fix crash when extracting sections from NCA with no data section (#5002)
Tested against Omega Strickers.
2023-05-17 19:27:49 +00:00
cb4b58052f Start GPU performance counter at 0 instead of host GPU value (#4992)
* Start performance counter at 0 instead of host perf counter value

* whitespace

* init _firstTimestamp in constructer per feedback

* change comment

* punctuation

* address feedback

* revise comment
2023-05-17 15:38:59 -03:00
f8cdd5f484 macos: Fix relaunch with updater when no arguments were provided to the emulator (#4987)
The updater still seems to be erroring when replacing installed folder under normal operations.
2023-05-17 19:02:15 +02:00
22202be394 [GUI] Fix always hide cursor mode not hiding the cursor until it was moved (#4927)
* gtk: Add missing isMouseInClient check for hide-cursor

* ava: Add missing events and default isCursorInRenderer to true

This is necessary because we don't receive a initial PointerEnter event for some reason.
2023-05-14 16:34:31 +02:00
17ba217940 Vulkan: Device map buffers written more than flushed (#4911) 2023-05-13 15:15:05 +02:00
aae4595bdb Add timeout of 35 minutes to workflow jobs (#4928) 2023-05-13 13:24:43 +02:00
880fd3cfcb audio: sdl2: Do not report 5.1 if the device doesn't support it (#4908)
* amadeus: adjust VirtualDevice channel configuration reporting with HardwareDevice

* audio: sdl2: Do not report 5.1 if device doesn't support it

SDL2 5.1 to Stereo conversion is terrible and make everything sound
quiet.

Let's not expose 5.1 if not truly supported by the device.
2023-05-13 07:15:16 +00:00
178 changed files with 4844 additions and 2695 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'
- 'README.md'
concurrency:
group: pr-checks-${{ github.event.number }}
cancel-in-progress: true
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
@ -27,6 +31,7 @@ jobs:
build:
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
@ -109,6 +114,7 @@ jobs:
build_macos:
name: macOS Universal (${{ matrix.configuration }})
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
matrix:
configuration: [ Debug, Release ]
@ -150,4 +156,4 @@ jobs:
with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_ava/*.tar.gz"
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request'

View File

@ -12,6 +12,7 @@ concurrency: flatpak-release
jobs:
release:
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
runs-on: ubuntu-latest
env:

View File

@ -7,6 +7,7 @@ jobs:
pr_comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
steps:
- uses: actions/github-script@v6
with:
@ -65,4 +66,4 @@ jobs:
} else {
core.info(`Creating a comment`);
await github.rest.issues.createComment({repo, owner, issue_number, body});
}
}

View File

@ -33,7 +33,7 @@ jobs:
shell: bash
- name: Create tag
uses: actions/github-script@v5
uses: actions/github-script@v6
with:
script: |
github.rest.git.createRef({
@ -46,6 +46,7 @@ jobs:
release:
name: Release ${{ matrix.OS_NAME }}
runs-on: ${{ matrix.os }}
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
@ -143,13 +144,14 @@ jobs:
macos_release:
name: Release MacOS universal
runs-on: ubuntu-latest
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
with:
global-json-file: global.json
- name: Setup LLVM 14
run: |
wget https://apt.llvm.org/llvm.sh
@ -205,4 +207,4 @@ jobs:
needs: release
with:
ryujinx_version: "1.1.${{ github.run_number }}"
secrets: inherit
secrets: inherit

View File

@ -3,17 +3,17 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="0.10.19" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
<PackageVersion Include="Avalonia" Version="0.10.21" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.21" />
<PackageVersion Include="Avalonia.Desktop" Version="0.10.21" />
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.21" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.21" />
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
<PackageVersion Include="DynamicData" Version="7.13.5" />
<PackageVersion Include="DynamicData" Version="7.14.2" />
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
@ -21,7 +21,7 @@
<PackageVersion Include="LibHac" Version="0.18.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<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="MsgPack.Cli" Version="1.0.1" />
<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"
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

@ -27,13 +27,31 @@ error_handler() {
trap 'error_handler ${LINENO}' ERR
# 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
# Wait for Ryujinx to exit.
# If the main process is still acitve, we wait for 1 second and check it again.
# After the fifth time checking, this script exits with status 1.
attempt=0
while true; do
if lsof -p $APP_PID +r 1 &>/dev/null || ps -p "$APP_PID" &>/dev/null; then
if [ "$attempt" -eq 4 ]; then
exit 1
fi
sleep 1
else
break
fi
(( attempt++ ))
done
sleep 1
# Now replace and reopen.
rm -rf "$INSTALL_DIRECTORY"
mv "$NEW_APP_DIRECTORY" "$INSTALL_DIRECTORY"
open -a "$INSTALL_DIRECTORY" --args "$APP_ARGUMENTS"
if [ "$#" -le 3 ]; then
open -a "$INSTALL_DIRECTORY"
else
open -a "$INSTALL_DIRECTORY" --args "$APP_ARGUMENTS"
fi

View File

@ -6,10 +6,11 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace ARMeilleure.Translation.Cache
{
static class JitCache
static partial class JitCache
{
private const int PageSize = 4 * 1024;
private const int PageMask = PageSize - 1;
@ -27,6 +28,10 @@ namespace ARMeilleure.Translation.Cache
private static readonly object _lock = new object();
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)
{
if (_initialized) return;
@ -36,7 +41,11 @@ namespace ARMeilleure.Translation.Cache
if (_initialized) return;
_jitRegion = new ReservedRegion(allocator, CacheSize);
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
}
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
@ -77,7 +86,14 @@ namespace ARMeilleure.Translation.Cache
Marshal.Copy(code, 0, funcPtr, 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);

View File

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

View File

@ -5,6 +5,7 @@ using Ryujinx.Memory;
using Ryujinx.SDL2.Common;
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
@ -18,6 +19,13 @@ namespace Ryujinx.Audio.Backends.SDL2
private readonly ManualResetEvent _pauseEvent;
private readonly ConcurrentDictionary<SDL2HardwareDeviceSession, byte> _sessions;
private bool _supportSurroundConfiguration;
// TODO: Add this to SDL2-CS
// NOTE: We use a DllImport here because of marshaling issue for spec.
[DllImport("SDL2")]
private static extern int SDL_GetDefaultAudioInfo(IntPtr name, out SDL_AudioSpec spec, int isCapture);
public SDL2HardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
@ -25,6 +33,20 @@ namespace Ryujinx.Audio.Backends.SDL2
_sessions = new ConcurrentDictionary<SDL2HardwareDeviceSession, byte>();
SDL2Driver.Instance.Initialize();
int res = SDL_GetDefaultAudioInfo(IntPtr.Zero, out var spec, 0);
if (res != 0)
{
Logger.Error?.Print(LogClass.Application,
$"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\"");
_supportSurroundConfiguration = true;
}
else
{
_supportSurroundConfiguration = spec.channels >= 6;
}
}
public static bool IsSupported => IsSupportedInternal();
@ -164,6 +186,11 @@ namespace Ryujinx.Audio.Backends.SDL2
public bool SupportsChannelCount(uint channelCount)
{
if (channelCount == 6)
{
return _supportSurroundConfiguration;
}
return true;
}

View File

@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Renderer.Device
/// <param name="name">The name of the <see cref="VirtualDevice"/>.</param>
/// <param name="channelCount">The count of channels supported by the <see cref="VirtualDevice"/>.</param>
/// <param name="isExternalOutput">Indicate if the <see cref="VirtualDevice"/> is provided by an external interface.</param>
private VirtualDevice(string name, uint channelCount, bool isExternalOutput)
public VirtualDevice(string name, uint channelCount, bool isExternalOutput)
{
Name = name;
ChannelCount = channelCount;

View File

@ -1,3 +1,4 @@
using Ryujinx.Audio.Integration;
using System.Collections.Generic;
namespace Ryujinx.Audio.Renderer.Device
@ -22,7 +23,23 @@ namespace Ryujinx.Audio.Renderer.Device
/// The current active <see cref="VirtualDevice"/>.
/// </summary>
// TODO: make this configurable
public VirtualDevice ActiveDevice = VirtualDevice.Devices[2];
public VirtualDevice ActiveDevice { get; }
public VirtualDeviceSessionRegistry(IHardwareDeviceDriver driver)
{
uint channelCount;
if (driver.GetRealDeviceDriver().SupportsChannelCount(6))
{
channelCount = 6;
}
else
{
channelCount = 2;
}
ActiveDevice = new VirtualDevice("AudioTvOutput", channelCount, false);
}
/// <summary>
/// Get the associated <see cref="T:VirtualDeviceSession[]"/> from an AppletResourceId.

View File

@ -31,7 +31,6 @@ namespace Ryujinx.Audio.Renderer.Server
private AudioRendererRenderingDevice _renderingDevice;
private AudioRendererExecutionMode _executionMode;
private IWritableEvent _systemEvent;
private ManualResetEvent _terminationEvent;
private MemoryPoolState _dspMemoryPoolState;
private VoiceContext _voiceContext;
private MixContext _mixContext;
@ -83,7 +82,6 @@ namespace Ryujinx.Audio.Renderer.Server
public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent)
{
_manager = manager;
_terminationEvent = new ManualResetEvent(false);
_dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp);
_voiceContext = new VoiceContext();
_mixContext = new MixContext();
@ -387,11 +385,6 @@ namespace Ryujinx.Audio.Renderer.Server
_isActive = false;
}
if (_executionMode == AudioRendererExecutionMode.Auto)
{
_terminationEvent.WaitOne();
}
Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}");
}
@ -668,8 +661,6 @@ namespace Ryujinx.Audio.Renderer.Server
{
if (_isActive)
{
_terminationEvent.Reset();
if (!_manager.Processor.HasRemainingCommands(_sessionId))
{
GenerateCommandList(out CommandList commands);
@ -686,10 +677,6 @@ namespace Ryujinx.Audio.Renderer.Server
_isDspRunningBehind = true;
}
}
else
{
_terminationEvent.Set();
}
}
}
@ -857,7 +844,6 @@ namespace Ryujinx.Audio.Renderer.Server
}
_manager.Unregister(this);
_terminationEvent.Dispose();
_workBufferMemoryPin.Dispose();
if (MemoryManager is IRefCounted rc)

View File

@ -86,12 +86,14 @@ namespace Ryujinx.Ava
private KeyboardHotkeyState _prevHotkeyState;
private long _lastCursorMoveTime;
private bool _isCursorInRenderer;
private bool _isCursorInRenderer = true;
private bool _isStopped;
private bool _isActive;
private bool _renderingStarted;
private ManualResetEvent _gpuDoneEvent;
private IRenderer _renderer;
private readonly Thread _renderingThread;
private readonly CancellationTokenSource _gpuCancellationTokenSource;
@ -160,7 +162,9 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.HideCursor.Event += HideCursorState_Changed;
_topLevel.PointerMoved += TopLevel_PointerMoved;
_topLevel.PointerMoved += TopLevel_PointerEnterOrMoved;
_topLevel.PointerEnter += TopLevel_PointerEnterOrMoved;
_topLevel.PointerLeave += TopLevel_PointerLeave;
if (OperatingSystem.IsWindows())
{
@ -181,9 +185,10 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
_gpuCancellationTokenSource = new CancellationTokenSource();
_gpuDoneEvent = new ManualResetEvent(false);
}
private void TopLevel_PointerMoved(object sender, PointerEventArgs e)
private void TopLevel_PointerEnterOrMoved(object sender, PointerEventArgs e)
{
if (sender is MainWindow window)
{
@ -201,6 +206,12 @@ namespace Ryujinx.Ava
}
}
}
private void TopLevel_PointerLeave(object sender, PointerEventArgs e)
{
_isCursorInRenderer = false;
}
private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e)
{
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
@ -262,7 +273,7 @@ namespace Ryujinx.Ava
string directory = AppDataManager.Mode switch
{
AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
_ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx")
};
@ -415,10 +426,10 @@ namespace Ryujinx.Ava
_isActive = false;
if (_renderingThread.IsAlive)
{
_renderingThread.Join();
}
// NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
// We only need to wait for all commands submitted during the main gpu loop to be processed.
_gpuDoneEvent.WaitOne();
_gpuDoneEvent.Dispose();
DisplaySleep.Restore();
@ -446,7 +457,9 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel;
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing;
_topLevel.PointerMoved -= TopLevel_PointerMoved;
_topLevel.PointerMoved -= TopLevel_PointerEnterOrMoved;
_topLevel.PointerEnter -= TopLevel_PointerEnterOrMoved;
_topLevel.PointerLeave -= TopLevel_PointerLeave;
_gpuCancellationTokenSource.Cancel();
_gpuCancellationTokenSource.Dispose();
@ -907,6 +920,14 @@ namespace Ryujinx.Ava
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);

View File

@ -74,6 +74,13 @@
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
"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",
"SettingsTabGeneral": "User Interface",
"SettingsTabGeneralGeneral": "General",
@ -216,26 +223,17 @@
"ControllerSettingsDPadDown": "Down",
"ControllerSettingsDPadLeft": "Left",
"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",
"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",
"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",
"ControllerSettingsTriggersRight": "Triggers Right",
"ControllerSettingsTriggersButtonsLeft": "Trigger Buttons Left",
@ -291,6 +289,7 @@
"ControllerSettingsSaveProfileToolTip": "Save Profile",
"MenuBarFileToolsTakeScreenshot": "Take Screenshot",
"MenuBarFileToolsHideUi": "Hide UI",
"GameListContextMenuRunApplication": "Run Application",
"GameListContextMenuToggleFavorite": "Toggle Favorite",
"GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game",
"SettingsTabGeneralTheme": "Theme",
@ -629,7 +628,7 @@
"Search": "Search",
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
"Recover": "Recover",
"UserProfilesRecoverHeading" : "Saves were found for the following accounts",
"UserProfilesRecoverHeading": "Saves were found for the following accounts",
"UserProfilesRecoverEmptyList": "No profiles to recover",
"GraphicsAATooltip": "Applies anti-aliasing to the game render",
"GraphicsAALabel": "Anti-Aliasing:",
@ -641,10 +640,12 @@
"SmaaMedium": "SMAA Medium",
"SmaaHigh": "SMAA High",
"SmaaUltra": "SMAA Ultra",
"UserEditorTitle" : "Edit User",
"UserEditorTitleCreate" : "Create User",
"UserEditorTitle": "Edit User",
"UserEditorTitleCreate": "Create User",
"SettingsTabNetworkInterface": "Network Interface:",
"NetworkInterfaceTooltip": "The network interface used for LAN features",
"NetworkInterfaceDefault": "Default",
"PackagingShaders": "Packaging Shaders"
}
"PackagingShaders": "Packaging Shaders",
"AboutChangelogButton": "View Changelog on GitHub",
"AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser."
}

View File

@ -193,7 +193,7 @@ namespace Ryujinx.Ava.Common
if (nca.Header.ContentType == NcaContentType.Program)
{
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
{
patchNca = nca;
}

View File

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

View File

@ -3,6 +3,9 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale">
<MenuItem
Click="RunApplication_Click"
Header="{locale:Locale GameListContextMenuRunApplication}" />
<MenuItem
Click="ToggleFavorite_Click"
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);
}
}
public void RunApplication_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
viewModel.LoadApplication(viewModel.SelectedApplication.Path);
}
}
}
}

View File

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

View File

@ -6,7 +6,6 @@ using Avalonia.Threading;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Logging;
using System;
@ -19,7 +18,7 @@ namespace Ryujinx.Ava.UI.Helpers
{
private static bool _isChoiceDialogOpen;
public async static Task<UserResult> ShowContentDialog(
private async static Task<UserResult> ShowContentDialog(
string title,
object content,
string primaryButton,
@ -67,7 +66,7 @@ namespace Ryujinx.Ava.UI.Helpers
return result;
}
private async static Task<UserResult> ShowTextDialog(
public async static Task<UserResult> ShowTextDialog(
string title,
string primaryText,
string secondaryText,
@ -319,7 +318,7 @@ namespace Ryujinx.Ava.UI.Helpers
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()
{

View File

@ -3,21 +3,20 @@ using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Helpers
{
public static class NotificationHelper
{
private const int MaxNotifications = 4;
private const int MaxNotifications = 4;
private const int NotificationDelayInMs = 5000;
private static WindowNotificationManager _notificationManager;
private static readonly ManualResetEvent _templateAppliedEvent = new(false);
private static readonly BlockingCollection<Notification> _notifications = new();
public static void SetNotificationManager(Window host)
@ -29,25 +28,31 @@ namespace Ryujinx.Ava.UI.Helpers
Margin = new Thickness(0, 0, 15, 40)
};
var maybeAsyncWorkQueue = new Lazy<AsyncWorkQueue<Notification>>(
() => new AsyncWorkQueue<Notification>(notification =>
{
Dispatcher.UIThread.Post(() =>
{
_notificationManager.Show(notification);
});
},
"UI.NotificationThread",
_notifications),
LazyThreadSafetyMode.ExecutionAndPublication);
_notificationManager.TemplateApplied += (sender, args) =>
{
_templateAppliedEvent.Set();
// NOTE: Force creation of the AsyncWorkQueue.
_ = maybeAsyncWorkQueue.Value;
};
Task.Run(async () =>
host.Closing += (sender, args) =>
{
_templateAppliedEvent.WaitOne();
foreach (var notification in _notifications.GetConsumingEnumerable())
if (maybeAsyncWorkQueue.IsValueCreated)
{
Dispatcher.UIThread.Post(() =>
{
_notificationManager.Show(notification);
});
await Task.Delay(NotificationDelayInMs / MaxNotifications);
maybeAsyncWorkQueue.Value.Dispose();
}
});
};
}
public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null)

View File

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

View File

@ -1529,6 +1529,8 @@ namespace Ryujinx.Ava.UI.ViewModels
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
appMetadata.LastPlayed = DateTime.UtcNow;
});
}

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

@ -311,7 +311,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
_gpuIds = new List<string>();
List<string> names = new();
var devices = VulkanRenderer.GetPhysicalDevices(Vk.GetApi());
var devices = VulkanRenderer.GetPhysicalDevices();
if (devices.Length == 0)
{

View File

@ -4,7 +4,6 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
@ -13,18 +12,18 @@ using Ryujinx.Input;
using Ryujinx.Input.Assigner;
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 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();

View File

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

View File

@ -6,44 +6,42 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller;
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();
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,
AltSlot = config.AltSlot,
DsuServerHost = config.DsuServerHost,
DsuServerPort = config.DsuServerPort,
MirrorInput = config.MirrorInput,
EnableMotion = config.EnableMotion,
Sensitivity = config.Sensitivity,
GyroDeadzone = config.GyroDeadzone,
EnableCemuHookMotion = config.EnableCemuHookMotion
};
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],
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave],
@ -53,16 +51,15 @@ namespace Ryujinx.Ava.UI.Windows
};
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.Slot = content._viewmodel.Slot;
config.EnableMotion = content._viewmodel.EnableMotion;
config.Sensitivity = content._viewmodel.Sensitivity;
config.GyroDeadzone = content._viewmodel.GyroDeadzone;
config.AltSlot = content._viewmodel.AltSlot;
config.DsuServerHost = content._viewmodel.DsuServerHost;
config.DsuServerPort = content._viewmodel.DsuServerPort;
config.EnableCemuHookMotion = content._viewmodel.EnableCemuHookMotion;
config.MirrorInput = content._viewmodel.MirrorInput;
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.Slot = content._viewModel.Slot;
config.Sensitivity = content._viewModel.Sensitivity;
config.GyroDeadzone = content._viewModel.GyroDeadzone;
config.AltSlot = content._viewModel.AltSlot;
config.DsuServerHost = content._viewModel.DsuServerHost;
config.DsuServerPort = content._viewModel.DsuServerPort;
config.EnableCemuHookMotion = content._viewModel.EnableCemuHookMotion;
config.MirrorInput = content._viewModel.MirrorInput;
};
await contentDialog.ShowAsync();

View File

@ -1,11 +1,14 @@
<UserControl
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
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">
<Grid Margin="10">
<Grid.RowDefinitions>

View File

@ -6,36 +6,37 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller;
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();
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();
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],
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave],
@ -43,14 +44,14 @@ namespace Ryujinx.Ava.UI.Windows
CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose],
Content = content,
};
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.StrongRumble = content._viewmodel.StrongRumble;
config.WeakRumble = content._viewmodel.WeakRumble;
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.StrongRumble = content._viewModel.StrongRumble;
config.WeakRumble = content._viewModel.WeakRumble;
};
await contentDialog.ShowAsync();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -23,6 +23,7 @@ using Ryujinx.Ui.Common.Helper;
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using InputManager = Ryujinx.Input.HLE.InputManager;
@ -258,7 +259,64 @@ namespace Ryujinx.Ava.UI.Windows
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)
{
@ -268,6 +326,20 @@ namespace Ryujinx.Ava.UI.Windows
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)
{
_deferLoad = false;
@ -447,14 +519,14 @@ namespace Ryujinx.Ava.UI.Windows
private void ConfirmExit()
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
{
ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
if (ViewModel.IsClosing)
{
Close();
}
});
if (ViewModel.IsClosing)
{
Close();
}
});
}
public async void LoadApplications()

View File

@ -22,9 +22,11 @@ namespace Ryujinx.Common
_cts = new CancellationTokenSource();
_queue = collection;
_workerAction = callback;
_workerThread = new Thread(DoWork) { Name = name };
_workerThread.IsBackground = true;
_workerThread = new Thread(DoWork)
{
Name = name,
IsBackground = true
};
_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
{
@ -27,6 +28,14 @@ namespace Ryujinx.Common.Logging
if (args.Data is not null)
{
if (args.Data is StackTrace trace)
{
sb.Append('\n');
sb.Append(trace);
return sb.ToString();
}
sb.Append(' ');
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.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>();
@ -17,7 +17,7 @@ namespace Ryujinx.Common.Logging
}
StringBuilder sb = StringBuilderPool.Allocate();
try
{
Format(sb, dynamicObject);

View File

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

View File

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

View File

@ -1,3 +1,4 @@
using Ryujinx.Common.Logging.Targets;
using Ryujinx.Common.SystemInterop;
using System;
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)]
public void PrintStub(LogClass logClass, string message = "", [CallerMemberName] string caller = "")
{
@ -122,7 +133,7 @@ namespace Ryujinx.Common.Logging
AsyncLogTargetOverflowAction.Discard));
Notice = new Log(LogLevel.Notice);
// Enable important log levels before configuration is loaded
Error = new Log(LogLevel.Error);
Warning = new Log(LogLevel.Warning);
@ -221,4 +232,4 @@ namespace Ryujinx.Common.Logging
m_EnabledClasses[(int)logClass] = enabled;
}
}
}
}

View File

@ -2,7 +2,7 @@
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.Common.Logging
namespace Ryujinx.Common.Logging.Targets
{
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
{
@ -38,4 +39,4 @@ namespace Ryujinx.Common.Logging
Console.ResetColor();
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -39,6 +39,7 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsViewportMask;
public readonly bool SupportsViewportSwizzle;
public readonly bool SupportsIndirectParameters;
public readonly bool SupportsDepthClipControl;
public readonly uint MaximumUniformBuffersPerStage;
public readonly uint MaximumStorageBuffersPerStage;
@ -85,6 +86,7 @@ namespace Ryujinx.Graphics.GAL
bool supportsViewportMask,
bool supportsViewportSwizzle,
bool supportsIndirectParameters,
bool supportsDepthClipControl,
uint maximumUniformBuffersPerStage,
uint maximumStorageBuffersPerStage,
uint maximumTexturesPerStage,
@ -127,6 +129,7 @@ namespace Ryujinx.Graphics.GAL
SupportsViewportMask = supportsViewportMask;
SupportsViewportSwizzle = supportsViewportSwizzle;
SupportsIndirectParameters = supportsIndirectParameters;
SupportsDepthClipControl = supportsDepthClipControl;
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
MaximumTexturesPerStage = maximumTexturesPerStage;

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,179 @@
using System;
using System.Collections.ObjectModel;
namespace Ryujinx.Graphics.GAL
{
public enum ResourceType : byte
{
UniformBuffer,
StorageBuffer,
Texture,
Sampler,
TextureAndSampler,
Image,
BufferTexture,
BufferImage
}
public enum ResourceAccess : byte
{
None = 0,
Read = 1,
Write = 2,
ReadWrite = Read | Write
}
[Flags]
public enum ResourceStages : byte
{
None = 0,
Compute = 1 << 0,
Vertex = 1 << 1,
TessellationControl = 1 << 2,
TessellationEvaluation = 1 << 3,
Geometry = 1 << 4,
Fragment = 1 << 5
}
public readonly struct ResourceDescriptor : IEquatable<ResourceDescriptor>
{
public int Binding { get; }
public int Count { get; }
public ResourceType Type { get; }
public ResourceStages Stages { get; }
public ResourceDescriptor(int binding, int count, ResourceType type, ResourceStages stages)
{
Binding = binding;
Count = count;
Type = type;
Stages = stages;
}
public override int GetHashCode()
{
return HashCode.Combine(Binding, Count, Type, Stages);
}
public override bool Equals(object obj)
{
return obj is ResourceDescriptor other && Equals(other);
}
public bool Equals(ResourceDescriptor other)
{
return Binding == other.Binding && Count == other.Count && Type == other.Type && Stages == other.Stages;
}
}
public readonly struct ResourceUsage : IEquatable<ResourceUsage>
{
public int Binding { get; }
public ResourceType Type { get; }
public ResourceStages Stages { get; }
public ResourceAccess Access { get; }
public ResourceUsage(int binding, ResourceType type, ResourceStages stages, ResourceAccess access)
{
Binding = binding;
Type = type;
Stages = stages;
Access = access;
}
public override int GetHashCode()
{
return HashCode.Combine(Binding, Type, Stages, Access);
}
public override bool Equals(object obj)
{
return obj is ResourceUsage other && Equals(other);
}
public bool Equals(ResourceUsage other)
{
return Binding == other.Binding && Type == other.Type && Stages == other.Stages && Access == other.Access;
}
}
public readonly struct ResourceDescriptorCollection
{
public ReadOnlyCollection<ResourceDescriptor> Descriptors { get; }
public ResourceDescriptorCollection(ReadOnlyCollection<ResourceDescriptor> descriptors)
{
Descriptors = descriptors;
}
public override int GetHashCode()
{
HashCode hasher = new HashCode();
if (Descriptors != null)
{
foreach (var descriptor in Descriptors)
{
hasher.Add(descriptor);
}
}
return hasher.ToHashCode();
}
public override bool Equals(object obj)
{
return obj is ResourceDescriptorCollection other && Equals(other);
}
public bool Equals(ResourceDescriptorCollection other)
{
if ((Descriptors == null) != (other.Descriptors == null))
{
return false;
}
if (Descriptors != null)
{
if (Descriptors.Count != other.Descriptors.Count)
{
return false;
}
for (int index = 0; index < Descriptors.Count; index++)
{
if (!Descriptors[index].Equals(other.Descriptors[index]))
{
return false;
}
}
}
return true;
}
}
public readonly struct ResourceUsageCollection
{
public ReadOnlyCollection<ResourceUsage> Usages { get; }
public ResourceUsageCollection(ReadOnlyCollection<ResourceUsage> usages)
{
Usages = usages;
}
}
public readonly struct ResourceLayout
{
public ReadOnlyCollection<ResourceDescriptorCollection> Sets { get; }
public ReadOnlyCollection<ResourceUsageCollection> SetUsages { get; }
public ResourceLayout(
ReadOnlyCollection<ResourceDescriptorCollection> sets,
ReadOnlyCollection<ResourceUsageCollection> setUsages)
{
Sets = sets;
SetUsages = setUsages;
}
}
}

View File

@ -1,24 +0,0 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.GAL
{
public readonly struct ShaderBindings
{
public IReadOnlyCollection<int> UniformBufferBindings { get; }
public IReadOnlyCollection<int> StorageBufferBindings { get; }
public IReadOnlyCollection<int> TextureBindings { get; }
public IReadOnlyCollection<int> ImageBindings { get; }
public ShaderBindings(
IReadOnlyCollection<int> uniformBufferBindings,
IReadOnlyCollection<int> storageBufferBindings,
IReadOnlyCollection<int> textureBindings,
IReadOnlyCollection<int> imageBindings)
{
UniformBufferBindings = uniformBufferBindings;
StorageBufferBindings = storageBufferBindings;
TextureBindings = textureBindings;
ImageBindings = imageBindings;
}
}
}

View File

@ -3,19 +3,22 @@ namespace Ryujinx.Graphics.GAL
public struct ShaderInfo
{
public int FragmentOutputMap { get; }
public ResourceLayout ResourceLayout { get; }
public ProgramPipelineState? State { get; }
public bool FromCache { get; set; }
public ShaderInfo(int fragmentOutputMap, ProgramPipelineState state, bool fromCache = false)
public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, ProgramPipelineState state, bool fromCache = false)
{
FragmentOutputMap = fragmentOutputMap;
ResourceLayout = resourceLayout;
State = state;
FromCache = fromCache;
}
public ShaderInfo(int fragmentOutputMap, bool fromCache = false)
public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, bool fromCache = false)
{
FragmentOutputMap = fragmentOutputMap;
ResourceLayout = resourceLayout;
State = null;
FromCache = fromCache;
}

View File

@ -7,24 +7,22 @@ namespace Ryujinx.Graphics.GAL
{
public string Code { get; }
public byte[] BinaryCode { get; }
public ShaderBindings Bindings { get; }
public ShaderStage Stage { get; }
public TargetLanguage Language { get; }
public ShaderSource(string code, byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language)
public ShaderSource(string code, byte[] binaryCode, ShaderStage stage, TargetLanguage language)
{
Code = code;
BinaryCode = binaryCode;
Bindings = bindings;
Stage = stage;
Language = language;
}
public ShaderSource(string code, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(code, null, bindings, stage, language)
public ShaderSource(string code, ShaderStage stage, TargetLanguage language) : this(code, null, stage, language)
{
}
public ShaderSource(byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, bindings, stage, language)
public ShaderSource(byte[] binaryCode, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, stage, language)
{
}
}

View File

@ -40,22 +40,6 @@ namespace Ryujinx.Graphics.Gpu
/// </summary>
public const int TotalTransformFeedbackBuffers = 4;
/// <summary>
/// Maximum number of textures on a single shader stage.
/// </summary>
/// <remarks>
/// The maximum number of textures is API limited, the hardware supports an unlimited amount.
/// </remarks>
public const int TotalTextures = 32;
/// <summary>
/// Maximum number of images on a single shader stage.
/// </summary>
/// <remarks>
/// The maximum number of images is API limited, the hardware supports an unlimited amount.
/// </remarks>
public const int TotalImages = 8;
/// <summary>
/// Maximum number of render target color buffers.
/// </summary>
@ -100,5 +84,15 @@ namespace Ryujinx.Graphics.Gpu
/// Expected byte alignment for storage buffers
/// </summary>
public const int StorageAlignment = 16;
/// <summary>
/// Number of the uniform buffer reserved by the driver to store the storage buffer base addresses.
/// </summary>
public const int DriverReservedUniformBuffer = 0;
/// <summary>
/// Maximum size that an storage buffer is assumed to have when the correct size is unknown.
/// </summary>
public const ulong MaxUnknownStorageSize = 0x100000;
}
}

View File

@ -162,7 +162,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags);
uint size;
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
{
// Only trust the SbDescriptor size if it comes from slot 0.
size = (uint)sbDescriptor.Size;
}
else
{
// TODO: Use full mapped size and somehow speed up buffer sync.
size = (uint)_channel.MemoryManager.GetMappedSize(sbDescriptor.PackAddress(), Constants.MaxUnknownStorageSize);
}
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags);
}
if ((_channel.BufferManager.HasUnalignedStorageBuffers) != hasUnaligned)

View File

@ -356,7 +356,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
_channel.BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags);
uint size;
if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer)
{
// Only trust the SbDescriptor size if it comes from slot 0.
size = (uint)sbDescriptor.Size;
}
else
{
// TODO: Use full mapped size and somehow speed up buffer sync.
size = (uint)_channel.MemoryManager.GetMappedSize(sbDescriptor.PackAddress(), Constants.MaxUnknownStorageSize);
}
_channel.BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags);
}
}
}
@ -759,7 +771,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
private void UpdateDepthMode()
{
_context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
DepthMode mode = GetDepthMode();
_pipeline.DepthMode = mode;
_context.Renderer.Pipeline.SetDepthMode(mode);
}
/// <summary>

View File

@ -99,6 +99,7 @@ namespace Ryujinx.Graphics.Gpu
private bool _pendingSync;
private long _modifiedSequence;
private ulong _firstTimestamp;
/// <summary>
/// Creates a new instance of the GPU emulation context.
@ -123,6 +124,8 @@ namespace Ryujinx.Graphics.Gpu
DeferredActions = new Queue<Action>();
PhysicalMemoryRegistry = new ConcurrentDictionary<ulong, PhysicalMemory>();
_firstTimestamp = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds);
}
/// <summary>
@ -217,7 +220,8 @@ namespace Ryujinx.Graphics.Gpu
/// <returns>The current GPU timestamp</returns>
public ulong GetTimestamp()
{
ulong ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds);
// Guest timestamp will start at 0, instead of host value.
ulong ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds) - _firstTimestamp;
if (GraphicsConfig.FastGpuTime)
{
@ -386,7 +390,6 @@ namespace Ryujinx.Graphics.Gpu
/// </summary>
public void Dispose()
{
Renderer.Dispose();
GPFifo.Dispose();
HostInitalized.Dispose();
@ -399,6 +402,8 @@ namespace Ryujinx.Graphics.Gpu
PhysicalMemoryRegistry.Clear();
RunDeferredActions();
Renderer.Dispose();
}
}
}

View File

@ -637,6 +637,33 @@ namespace Ryujinx.Graphics.Gpu.Memory
return UnpackPaFromPte(pte) + (va & PageMask);
}
/// <summary>
/// Translates a GPU virtual address and returns the number of bytes that are mapped after it.
/// </summary>
/// <param name="va">GPU virtual address to be translated</param>
/// <param name="maxSize">Maximum size in bytes to scan</param>
/// <returns>Number of bytes, 0 if unmapped</returns>
public ulong GetMappedSize(ulong va, ulong maxSize)
{
if (!ValidateAddress(va))
{
return 0;
}
ulong startVa = va;
ulong endVa = va + maxSize;
ulong pte = GetPte(va);
while (pte != PteUnmapped && va < endVa)
{
va += PageSize - (va & PageMask);
pte = GetPte(va);
}
return Math.Min(maxSize, va - startVa);
}
/// <summary>
/// Gets the kind of a given memory page.
/// This might indicate the type of resource that can be allocated on the page, and also texture tiling.

View File

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 4892;
private const uint CodeGenVersion = 5027;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";
@ -368,12 +368,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
if (hostCode != null)
{
bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
int fragmentOutputMap = hasFragmentShader ? shaders[5].Info.FragmentOutputMap : -1;
ShaderInfo shaderInfo = specState.PipelineState.HasValue
? new ShaderInfo(fragmentOutputMap, specState.PipelineState.Value, fromCache: true)
: new ShaderInfo(fragmentOutputMap, fromCache: true);
ShaderInfo shaderInfo = ShaderInfoBuilder.BuildForCache(context, shaders, specState.PipelineState);
IProgram hostProgram;
@ -385,6 +380,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
else
{
bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo);
}

View File

@ -491,23 +491,16 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{
ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length];
int fragmentOutputMap = -1;
ShaderInfoBuilder shaderInfoBuilder = new ShaderInfoBuilder(_context);
for (int index = 0; index < compilation.TranslatedStages.Length; index++)
{
ShaderProgram shader = compilation.TranslatedStages[index];
shaderSources[index] = CreateShaderSource(shader);
if (shader.Info.Stage == ShaderStage.Fragment)
{
fragmentOutputMap = shader.Info.FragmentOutputMap;
}
shaderInfoBuilder.AddStageInfo(shader.Info);
}
ShaderInfo shaderInfo = compilation.SpecializationState.PipelineState.HasValue
? new ShaderInfo(fragmentOutputMap, compilation.SpecializationState.PipelineState.Value, fromCache: true)
: new ShaderInfo(fragmentOutputMap, fromCache: true);
ShaderInfo shaderInfo = shaderInfoBuilder.Build(compilation.SpecializationState.PipelineState, fromCache: true);
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, shaderInfo);
CachedShaderProgram program = new CachedShaderProgram(hostProgram, compilation.SpecializationState, compilation.Shaders);

View File

@ -42,25 +42,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
int binaryCodeLength = reader.ReadInt32();
byte[] binaryCode = reader.ReadBytes(binaryCodeLength);
output.Add(new ShaderSource(binaryCode, GetBindings(stages, stage), stage, TargetLanguage.Spirv));
output.Add(new ShaderSource(binaryCode, stage, TargetLanguage.Spirv));
}
return output.ToArray();
}
private static ShaderBindings GetBindings(CachedShaderStage[] stages, ShaderStage stage)
{
for (int i = 0; i < stages.Length; i++)
{
CachedShaderStage currentStage = stages[i];
if (currentStage?.Info != null && currentStage.Info.Stage == stage)
{
return ShaderCache.GetBindings(currentStage.Info);
}
}
return new ShaderBindings(Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>());
}
}
}

View File

@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
namespace Ryujinx.Graphics.Gpu.Shader
{
@ -93,16 +94,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
Logger.Error?.Print(LogClass.Gpu, $"{resourceName} index {index} exceeds per stage limit of {maxPerStage}.");
}
return GetStageIndex() * (int)maxPerStage + index;
return GetStageIndex(_stageIndex) * (int)maxPerStage + index;
}
private int GetStageIndex()
public static int GetStageIndex(int stageIndex)
{
// This is just a simple remapping to ensure that most frequently used shader stages
// have the lowest binding numbers.
// This is useful because if we need to run on a system with a low limit on the bindings,
// then we can still get most games working as the most common shaders will have low binding numbers.
return _stageIndex switch
return stageIndex switch
{
4 => 1, // Fragment
3 => 2, // Geometry
@ -148,6 +149,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsViewportMask() => _context.Capabilities.SupportsViewportMask;
public bool QueryHostSupportsDepthClipControl() => _context.Capabilities.SupportsDepthClipControl;
/// <summary>
/// Converts a packed Maxwell texture format to the shader translator texture format.
/// </summary>

View File

@ -219,12 +219,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState);
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(-1));
ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info);
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
@ -363,6 +362,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
TranslatorContext previousStage = null;
ShaderInfoBuilder infoBuilder = new ShaderInfoBuilder(_context);
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
{
TranslatorContext currentStage = translatorContexts[stageIndex + 1];
@ -398,6 +399,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (program != null)
{
shaderSources.Add(CreateShaderSource(program));
infoBuilder.AddStageInfo(program.Info);
}
previousStage = currentStage;
@ -414,8 +416,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
ShaderSource[] shaderSourcesArray = shaderSources.ToArray();
int fragmentOutputMap = shaders[5]?.Info.FragmentOutputMap ?? -1;
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(fragmentOutputMap, pipeline));
ShaderInfo info = infoBuilder.Build(pipeline);
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
gpShaders = new CachedShaderProgram(hostProgram, specState, shaders);
@ -466,7 +469,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <returns>Shader source</returns>
public static ShaderSource CreateShaderSource(ShaderProgram program)
{
return new ShaderSource(program.Code, program.BinaryCode, GetBindings(program.Info), program.Info.Stage, program.Language);
return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language);
}
/// <summary>
@ -717,25 +720,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
};
}
/// <summary>
/// Gets information about the bindings used by a shader program.
/// </summary>
/// <param name="info">Shader program information to get the information from</param>
/// <returns>Shader bindings</returns>
public static ShaderBindings GetBindings(ShaderProgramInfo info)
{
var uniformBufferBindings = info.CBuffers.Select(x => x.Binding).ToArray();
var storageBufferBindings = info.SBuffers.Select(x => x.Binding).ToArray();
var textureBindings = info.Textures.Select(x => x.Binding).ToArray();
var imageBindings = info.Images.Select(x => x.Binding).ToArray();
return new ShaderBindings(
uniformBufferBindings,
storageBufferBindings,
textureBindings,
imageBindings);
}
/// <summary>
/// Creates shader translation options with the requested graphics API and flags.
/// The shader language is choosen based on the current configuration and graphics API.

View File

@ -0,0 +1,260 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Shader
{
/// <summary>
/// Shader info structure builder.
/// </summary>
class ShaderInfoBuilder
{
private const int TotalSets = 4;
private const int UniformSetIndex = 0;
private const int StorageSetIndex = 1;
private const int TextureSetIndex = 2;
private const int ImageSetIndex = 3;
private const ResourceStages SupportBufferStags =
ResourceStages.Compute |
ResourceStages.Vertex |
ResourceStages.Fragment;
private readonly GpuContext _context;
private int _fragmentOutputMap;
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
private readonly List<ResourceUsage>[] _resourceUsages;
/// <summary>
/// Creates a new shader info builder.
/// </summary>
/// <param name="context">GPU context that owns the shaders that will be added to the builder</param>
public ShaderInfoBuilder(GpuContext context)
{
_context = context;
_fragmentOutputMap = -1;
_resourceDescriptors = new List<ResourceDescriptor>[TotalSets];
_resourceUsages = new List<ResourceUsage>[TotalSets];
for (int index = 0; index < TotalSets; index++)
{
_resourceDescriptors[index] = new();
_resourceUsages[index] = new();
}
AddDescriptor(SupportBufferStags, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
}
/// <summary>
/// Adds information from a given shader stage.
/// </summary>
/// <param name="info">Shader stage information</param>
public void AddStageInfo(ShaderProgramInfo info)
{
if (info.Stage == ShaderStage.Fragment)
{
_fragmentOutputMap = info.FragmentOutputMap;
}
int stageIndex = GpuAccessorBase.GetStageIndex(info.Stage switch
{
ShaderStage.TessellationControl => 1,
ShaderStage.TessellationEvaluation => 2,
ShaderStage.Geometry => 3,
ShaderStage.Fragment => 4,
_ => 0
});
ResourceStages stages = info.Stage switch
{
ShaderStage.Compute => ResourceStages.Compute,
ShaderStage.Vertex => ResourceStages.Vertex,
ShaderStage.TessellationControl => ResourceStages.TessellationControl,
ShaderStage.TessellationEvaluation => ResourceStages.TessellationEvaluation,
ShaderStage.Geometry => ResourceStages.Geometry,
ShaderStage.Fragment => ResourceStages.Fragment,
_ => ResourceStages.None
};
int uniformsPerStage = (int)_context.Capabilities.MaximumUniformBuffersPerStage;
int storagesPerStage = (int)_context.Capabilities.MaximumStorageBuffersPerStage;
int texturesPerStage = (int)_context.Capabilities.MaximumTexturesPerStage;
int imagesPerStage = (int)_context.Capabilities.MaximumImagesPerStage;
int uniformBinding = 1 + stageIndex * uniformsPerStage;
int storageBinding = stageIndex * storagesPerStage;
int textureBinding = stageIndex * texturesPerStage * 2;
int imageBinding = stageIndex * imagesPerStage * 2;
AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
AddArrayDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false);
AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true);
AddUsage(info.Textures, stages, TextureSetIndex, isImage: false);
AddUsage(info.Images, stages, ImageSetIndex, isImage: true);
}
/// <summary>
/// Adds a resource descriptor 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 AddDescriptor(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
{
for (int index = 0; index < count; index++)
{
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding + index, 1, type, stages));
}
}
/// <summary>
/// Adds two interleaved groups of resources to the list of descriptors.
/// </summary>
/// <param name="stages">Shader stages where the resource is used</param>
/// <param name="type">Type of the first interleaved resource</param>
/// <param name="type2">Type of the second interleaved 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 AddDualDescriptor(ResourceStages stages, ResourceType type, ResourceType type2, int setIndex, int binding, int count)
{
AddDescriptor(stages, type, setIndex, binding, 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>
/// Adds buffer usage information to the list of usages.
/// </summary>
/// <param name="buffers">Buffers to be added</param>
/// <param name="stages">Stages where the buffers are used</param>
/// <param name="setIndex">Descriptor set index where the buffers will be bound</param>
/// <param name="isStorage">True for storage buffers, false for uniform buffers</param>
private void AddUsage(IEnumerable<BufferDescriptor> buffers, ResourceStages stages, int setIndex, bool isStorage)
{
foreach (BufferDescriptor buffer in buffers)
{
_resourceUsages[setIndex].Add(new ResourceUsage(
buffer.Binding,
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
stages,
buffer.Flags.HasFlag(BufferUsageFlags.Write) ? ResourceAccess.ReadWrite : ResourceAccess.Read));
}
}
/// <summary>
/// Adds texture usage information to the list of usages.
/// </summary>
/// <param name="textures">Textures to be added</param>
/// <param name="stages">Stages where the textures are used</param>
/// <param name="setIndex">Descriptor set index where the textures will be bound</param>
/// <param name="isImage">True for images, false for textures</param>
private void AddUsage(IEnumerable<TextureDescriptor> textures, ResourceStages stages, int setIndex, bool isImage)
{
foreach (TextureDescriptor texture in textures)
{
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
ResourceType type = isBuffer
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
_resourceUsages[setIndex].Add(new ResourceUsage(
texture.Binding,
type,
stages,
texture.Flags.HasFlag(TextureUsageFlags.ImageStore) ? ResourceAccess.ReadWrite : ResourceAccess.Read));
}
}
/// <summary>
/// Creates a new shader information structure from the added information.
/// </summary>
/// <param name="pipeline">Optional pipeline state for background shader compilation</param>
/// <param name="fromCache">Indicates if the shader comes from a disk cache</param>
/// <returns>Shader information</returns>
public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false)
{
var descriptors = new ResourceDescriptorCollection[TotalSets];
var usages = new ResourceUsageCollection[TotalSets];
for (int index = 0; index < TotalSets; index++)
{
descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
}
ResourceLayout resourceLayout = new ResourceLayout(descriptors.AsReadOnly(), usages.AsReadOnly());
if (pipeline.HasValue)
{
return new ShaderInfo(_fragmentOutputMap, resourceLayout, pipeline.Value, fromCache);
}
else
{
return new ShaderInfo(_fragmentOutputMap, resourceLayout, fromCache);
}
}
/// <summary>
/// Builds shader information for shaders from the disk cache.
/// </summary>
/// <param name="context">GPU context that owns the shaders</param>
/// <param name="programs">Shaders from the disk cache</param>
/// <param name="pipeline">Optional pipeline for background compilation</param>
/// <returns>Shader information</returns>
public static ShaderInfo BuildForCache(GpuContext context, IEnumerable<CachedShaderStage> programs, ProgramPipelineState? pipeline)
{
ShaderInfoBuilder builder = new ShaderInfoBuilder(context);
foreach (CachedShaderStage program in programs)
{
if (program?.Info != null)
{
builder.AddStageInfo(program.Info);
}
}
return builder.Build(pipeline, fromCache: true);
}
/// <summary>
/// Builds shader information for a compute shader.
/// </summary>
/// <param name="context">GPU context that owns the shader</param>
/// <param name="info">Compute shader information</param>
/// <param name="fromCache">True if the compute shader comes from a disk cache, false otherwise</param>
/// <returns>Shader information</returns>
public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false)
{
ShaderInfoBuilder builder = new ShaderInfoBuilder(context);
builder.AddStageInfo(info);
return builder.Build(null, fromCache);
}
}
}

View File

@ -736,6 +736,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
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>
/// Reads shader specialization state that has been serialized.
/// </summary>
@ -776,6 +789,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
ProgramPipelineState pipelineState = default;
dataReader.ReadWithMagicAndSize(ref pipelineState, PgpsMagic);
specState.PreparePipelineState(ref pipelineState);
specState.PipelineState = pipelineState;
}

View File

@ -163,6 +163,7 @@ namespace Ryujinx.Graphics.OpenGL
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
supportsDepthClipControl: true,
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
maximumStorageBuffersPerStage: 16,
maximumTexturesPerStage: 32,

View File

@ -3,6 +3,7 @@ using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
@ -102,13 +103,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine();
}
var cBufferDescriptors = context.Config.GetConstantBufferDescriptors();
if (cBufferDescriptors.Length != 0)
{
DeclareUniforms(context, cBufferDescriptors);
context.AppendLine();
}
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
var sBufferDescriptors = context.Config.GetStorageBufferDescriptors();
if (sBufferDescriptors.Length != 0)
@ -244,39 +239,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine();
}
bool isFragment = context.Config.Stage == ShaderStage.Fragment;
if (isFragment || context.Config.Stage == ShaderStage.Compute || context.Config.Stage == ShaderStage.Vertex)
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryEarlyZForce())
{
if (isFragment && context.Config.GpuAccessor.QueryEarlyZForce())
{
context.AppendLine("layout(early_fragment_tests) in;");
context.AppendLine();
}
if ((context.Config.UsedFeatures & (FeatureFlags.FragCoordXY | FeatureFlags.IntegerSampling)) != 0)
{
string stage = OperandManager.GetShaderStagePrefix(context.Config.Stage);
int scaleElements = context.Config.GetTextureDescriptors().Length + context.Config.GetImageDescriptors().Length;
if (isFragment)
{
scaleElements++; // Also includes render target scale, for gl_FragCoord.
}
DeclareSupportUniformBlock(context, context.Config.Stage, scaleElements);
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IntegerSampling) && scaleElements != 0)
{
AppendHelperFunction(context, $"Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_{stage}.glsl");
context.AppendLine();
}
}
else if (isFragment || context.Config.Stage == ShaderStage.Vertex)
{
DeclareSupportUniformBlock(context, context.Config.Stage, 0);
}
context.AppendLine("layout(early_fragment_tests) in;");
context.AppendLine();
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.AtomicMinMaxS32Shared) != 0)
@ -389,36 +355,38 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
};
}
private static void DeclareUniforms(CodeGenContext context, BufferDescriptor[] descriptors)
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
{
string ubSize = "[" + NumberFormatter.FormatInt(Constants.ConstantBufferSize / 16) + "]";
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing))
foreach (BufferDefinition buffer in buffers)
{
string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
ubName += "_" + DefaultNames.UniformNamePrefix;
string blockName = $"{ubName}_{DefaultNames.BlockSuffix}";
context.AppendLine($"layout (binding = {context.Config.FirstConstantBufferBinding}, std140) uniform {blockName}");
context.EnterScope();
context.AppendLine("vec4 " + DefaultNames.DataName + ubSize + ";");
context.LeaveScope($" {ubName}[{NumberFormatter.FormatInt(descriptors.Max(x => x.Slot) + 1)}];");
}
else
{
foreach (var descriptor in descriptors)
string layout = buffer.Layout switch
{
string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
BufferLayout.Std140 => "std140",
_ => "std430"
};
ubName += "_" + DefaultNames.UniformNamePrefix + descriptor.Slot;
context.AppendLine($"layout (binding = {buffer.Binding}, {layout}) uniform _{buffer.Name}");
context.EnterScope();
context.AppendLine($"layout (binding = {descriptor.Binding}, std140) uniform {ubName}");
context.EnterScope();
context.AppendLine("vec4 " + OperandManager.GetUbName(context.Config.Stage, descriptor.Slot, false) + ubSize + ";");
context.LeaveScope(";");
foreach (StructureField field in buffer.Type.Fields)
{
if (field.Type.HasFlag(AggregateType.Array))
{
string typeName = GetVarTypeName(context, field.Type & ~AggregateType.Array);
string arraySize = field.ArrayLength.ToString(CultureInfo.InvariantCulture);
context.AppendLine($"{typeName} {field.Name}[{arraySize}];");
}
else
{
string typeName = GetVarTypeName(context, field.Type);
context.AppendLine($"{typeName} {field.Name};");
}
}
context.LeaveScope($" {buffer.Name};");
context.AppendLine();
}
}
@ -759,39 +727,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine($"layout (location = {location}) patch out vec4 {name};");
}
private static void DeclareSupportUniformBlock(CodeGenContext context, ShaderStage stage, int scaleElements)
{
bool needsSupportBlock = stage == ShaderStage.Fragment ||
(context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable());
if (!needsSupportBlock && scaleElements == 0)
{
return;
}
context.AppendLine($"layout (binding = 0, std140) uniform {DefaultNames.SupportBlockName}");
context.EnterScope();
switch (stage)
{
case ShaderStage.Fragment:
case ShaderStage.Vertex:
context.AppendLine($"uint {DefaultNames.SupportBlockAlphaTestName};");
context.AppendLine($"bool {DefaultNames.SupportBlockIsBgraName}[{SupportBuffer.FragmentIsBgraCount}];");
context.AppendLine($"vec4 {DefaultNames.SupportBlockViewportInverse};");
context.AppendLine($"int {DefaultNames.SupportBlockFragmentScaleCount};");
break;
case ShaderStage.Compute:
context.AppendLine($"uint s_reserved[{SupportBuffer.ComputeRenderScaleOffset / SupportBuffer.FieldSize}];");
break;
}
context.AppendLine($"float {DefaultNames.SupportBlockRenderScaleName}[{SupportBuffer.RenderScaleMaxCount}];");
context.LeaveScope(";");
context.AppendLine();
}
private static void AppendHelperFunction(CodeGenContext context, string filename)
{
string code = EmbeddedResources.ReadAllText(filename);

View File

@ -15,18 +15,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public const string DataName = "data";
public const string SupportBlockName = "support_block";
public const string SupportBlockAlphaTestName = "s_alpha_test";
public const string SupportBlockIsBgraName = "s_is_bgra";
public const string SupportBlockViewportInverse = "s_viewport_inverse";
public const string SupportBlockFragmentScaleCount = "s_frag_scale_count";
public const string SupportBlockRenderScaleName = "s_render_scale";
public const string BlockSuffix = "block";
public const string UniformNamePrefix = "c";
public const string UniformNameSuffix = "data";
public const string LocalMemoryName = "local_mem";
public const string SharedMemoryName = "shared_mem";

View File

@ -1,19 +0,0 @@
ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex)
{
float scale = s_render_scale[samplerIndex];
if (scale == 1.0)
{
return inputVec;
}
return ivec2(vec2(inputVec) * scale);
}
int Helper_TextureSizeUnscale(int size, int samplerIndex)
{
float scale = s_render_scale[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 = 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(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(s_render_scale[1 + samplerIndex + 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(s_render_scale[1 + samplerIndex + s_frag_scale_count]);
if (scale == 1.0)
{
return size;
}
return int(float(size) / scale);
}

View File

@ -167,9 +167,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
case Instruction.Load:
return Load(context, operation);
case Instruction.LoadConstant:
return LoadConstant(context, operation);
case Instruction.LoadLocal:
return LoadLocal(context, operation);

View File

@ -83,7 +83,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.ImageAtomic, InstType.Special);
Add(Instruction.IsNan, InstType.CallUnary, "isnan");
Add(Instruction.Load, InstType.Special);
Add(Instruction.LoadConstant, InstType.Special);
Add(Instruction.LoadLocal, InstType.Special);
Add(Instruction.LoadShared, InstType.Special);
Add(Instruction.LoadStorage, InstType.Special);
@ -102,6 +101,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.MemoryBarrier, InstType.CallNullary, "memoryBarrier");
Add(Instruction.Minimum, InstType.CallBinary, "min");
Add(Instruction.MinimumU32, InstType.CallBinary, "min");
Add(Instruction.Modulo, InstType.CallBinary, "mod");
Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1);
Add(Instruction.MultiplyHighS32, InstType.CallBinary, HelperFunctionNames.MultiplyHighS32);
Add(Instruction.MultiplyHighU32, InstType.CallBinary, HelperFunctionNames.MultiplyHighU32);

View File

@ -97,30 +97,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCallBuilder.Append(str);
}
string ApplyScaling(string vector)
{
if (context.Config.Stage.SupportsRenderScale() &&
texOp.Inst == Instruction.ImageLoad &&
!isBindless &&
!isIndexed)
{
// Image scales start after texture ones.
int scaleIndex = context.Config.GetTextureDescriptors().Length + context.Config.FindImageDescriptorIndex(texOp);
if (pCount == 3 && isArray)
{
// The array index is not scaled, just x and y.
vector = $"ivec3(Helper_TexelFetchScale(({vector}).xy, {scaleIndex}), ({vector}).z)";
}
else if (pCount == 2 && !isArray)
{
vector = $"Helper_TexelFetchScale({vector}, {scaleIndex})";
}
}
return vector;
}
if (pCount > 1)
{
string[] elems = new string[pCount];
@ -130,7 +106,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
elems[index] = Src(AggregateType.S32);
}
Append(ApplyScaling($"ivec{pCount}({string.Join(", ", elems)})"));
Append($"ivec{pCount}({string.Join(", ", elems)})");
}
else
{
@ -215,29 +191,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return GenerateLoadOrStore(context, operation, isStore: false);
}
public static string LoadConstant(CodeGenContext context, AstOperation operation)
{
IAstNode src1 = operation.GetSource(0);
IAstNode src2 = operation.GetSource(1);
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
offsetExpr = Enclose(offsetExpr, src2, Instruction.ShiftRightS32, isLhs: true);
var config = context.Config;
bool indexElement = !config.GpuAccessor.QueryHostHasVectorIndexingBug();
if (src1 is AstOperand operand && operand.Type == OperandType.Constant)
{
bool cbIndexable = config.UsedFeatures.HasFlag(Translation.FeatureFlags.CbIndexing);
return OperandManager.GetConstantBufferName(operand.Value, offsetExpr, config.Stage, cbIndexable, indexElement);
}
else
{
string slotExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
return OperandManager.GetConstantBufferName(slotExpr, offsetExpr, config.Stage, indexElement);
}
}
public static string LoadLocal(CodeGenContext context, AstOperation operation)
{
return LoadLocalOrShared(context, operation, DefaultNames.LocalMemoryName);
@ -607,53 +560,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
}
string ApplyScaling(string vector)
{
if (intCoords)
{
if (context.Config.Stage.SupportsRenderScale() &&
!isBindless &&
!isIndexed)
{
int index = context.Config.FindTextureDescriptorIndex(texOp);
if (pCount == 3 && isArray)
{
// The array index is not scaled, just x and y.
vector = "ivec3(Helper_TexelFetchScale((" + vector + ").xy, " + index + "), (" + vector + ").z)";
}
else if (pCount == 2 && !isArray)
{
vector = "Helper_TexelFetchScale(" + vector + ", " + index + ")";
}
}
}
return vector;
}
string ApplyBias(string vector)
{
int gatherBiasPrecision = context.Config.GpuAccessor.QueryHostGatherBiasPrecision();
if (isGather && gatherBiasPrecision != 0)
{
// GPU requires texture gather to be slightly offset to match NVIDIA behaviour when point is exactly between two texels.
// Offset by the gather precision divided by 2 to correct for rounding.
if (pCount == 1)
{
vector = $"{vector} + (1.0 / (float(textureSize({samplerName}, 0)) * float({1 << (gatherBiasPrecision + 1)})))";
}
else
{
vector = $"{vector} + (1.0 / (vec{pCount}(textureSize({samplerName}, 0).{"xyz".Substring(0, pCount)}) * float({1 << (gatherBiasPrecision + 1)})))";
}
}
return vector;
}
Append(ApplyBias(ApplyScaling(AssemblePVector(pCount))));
Append(AssemblePVector(pCount));
string AssembleDerivativesVector(int count)
{
@ -773,7 +680,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
else
{
(TextureDescriptor descriptor, int descriptorIndex) = context.Config.FindTextureDescriptor(texOp);
TextureDescriptor descriptor = context.Config.FindTextureDescriptor(texOp);
bool hasLod = !descriptor.Type.HasFlag(SamplerType.Multisample) && descriptor.Type != SamplerType.TextureBuffer;
string texCall;
@ -790,14 +697,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
texCall = $"textureSize({samplerName}){GetMask(texOp.Index)}";
}
if (context.Config.Stage.SupportsRenderScale() &&
(texOp.Index < 2 || (texOp.Type & SamplerType.Mask) == SamplerType.Texture3D) &&
!isBindless &&
!isIndexed)
{
texCall = $"Helper_TextureSizeUnscale({texCall}, {descriptorIndex})";
}
return texCall;
}
}
@ -809,9 +708,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
string varName;
AggregateType varType;
int srcIndex = 0;
int inputsCount = isStore ? operation.SourcesCount - 1 : operation.SourcesCount;
switch (storageKind)
{
case StorageKind.ConstantBuffer:
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.");
}
int binding = bindingIndex.Value;
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[binding];
if (!(operation.GetSource(srcIndex++) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
StructureField field = buffer.Type.Fields[fieldIndex.Value];
varName = $"{buffer.Name}.{field.Name}";
varType = field.Type;
break;
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
@ -864,40 +783,39 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
varName = $"gl_out[{expr}].{varName}";
}
}
int firstSrcIndex = srcIndex;
int inputsCount = isStore ? operation.SourcesCount - 1 : operation.SourcesCount;
for (; srcIndex < inputsCount; srcIndex++)
{
IAstNode src = operation.GetSource(srcIndex);
if ((varType & AggregateType.ElementCountMask) != 0 &&
srcIndex == inputsCount - 1 &&
src is AstOperand elementIndex &&
elementIndex.Type == OperandType.Constant)
{
varName += "." + "xyzw"[elementIndex.Value & 3];
}
else if (srcIndex == firstSrcIndex && context.Config.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
{
// GLSL requires that for tessellation control shader outputs,
// that the index expression must be *exactly* "gl_InvocationID",
// otherwise the compilation fails.
// TODO: Get rid of this and use expression propagation to make sure we generate the correct code from IR.
varName += "[gl_InvocationID]";
}
else
{
varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]";
}
}
break;
default:
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
}
int firstSrcIndex = srcIndex;
for (; srcIndex < inputsCount; srcIndex++)
{
IAstNode src = operation.GetSource(srcIndex);
if ((varType & AggregateType.ElementCountMask) != 0 &&
srcIndex == inputsCount - 1 &&
src is AstOperand elementIndex &&
elementIndex.Type == OperandType.Constant)
{
varName += "." + "xyzw"[elementIndex.Value & 3];
}
else if (srcIndex == firstSrcIndex && context.Config.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
{
// GLSL requires that for tessellation control shader outputs,
// that the index expression must be *exactly* "gl_InvocationID",
// otherwise the compilation fails.
// TODO: Get rid of this and use expression propagation to make sure we generate the correct code from IR.
varName += "[gl_InvocationID]";
}
else
{
varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]";
}
}
if (isStore)
{
varType &= AggregateType.ElementTypeMask;

View File

@ -27,7 +27,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IoVariable.FragmentCoord => ("gl_FragCoord", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.FragmentOutputColor => GetFragmentOutputColorVariableName(config, location),
IoVariable.FragmentOutputDepth => ("gl_FragDepth", AggregateType.FP32),
IoVariable.FragmentOutputIsBgra => (DefaultNames.SupportBlockIsBgraName, AggregateType.Array | AggregateType.Bool),
IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.FrontFacing => ("gl_FrontFacing", AggregateType.Bool),
@ -46,8 +45,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IoVariable.SubgroupLaneId => GetSubgroupInvocationIdVariableName(config),
IoVariable.SubgroupLeMask => GetSubgroupMaskVariableName(config, "Le"),
IoVariable.SubgroupLtMask => GetSubgroupMaskVariableName(config, "Lt"),
IoVariable.SupportBlockRenderScale => (DefaultNames.SupportBlockRenderScaleName, AggregateType.Array | AggregateType.FP32),
IoVariable.SupportBlockViewInverse => (DefaultNames.SupportBlockViewportInverse, AggregateType.Vector2 | AggregateType.FP32),
IoVariable.TessellationCoord => ("gl_TessCoord", AggregateType.Vector3 | AggregateType.FP32),
IoVariable.TessellationLevelInner => ("gl_TessLevelInner", AggregateType.Array | AggregateType.FP32),
IoVariable.TessellationLevelOuter => ("gl_TessLevelOuter", AggregateType.Array | AggregateType.FP32),

View File

@ -36,63 +36,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
OperandType.Argument => GetArgumentName(operand.Value),
OperandType.Constant => NumberFormatter.FormatInt(operand.Value),
OperandType.ConstantBuffer => GetConstantBufferName(operand, context.Config),
OperandType.LocalVariable => _locals[operand],
OperandType.Undefined => DefaultNames.UndefinedName,
_ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\".")
};
}
private static string GetConstantBufferName(AstOperand operand, ShaderConfig config)
{
return GetConstantBufferName(operand.CbufSlot, operand.CbufOffset, config.Stage, config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing));
}
public static string GetConstantBufferName(int slot, int offset, ShaderStage stage, bool cbIndexable)
{
return $"{GetUbName(stage, slot, cbIndexable)}[{offset >> 2}].{GetSwizzleMask(offset & 3)}";
}
private static string GetVec4Indexed(string vectorName, string indexExpr, bool indexElement)
{
if (indexElement)
{
return $"{vectorName}[{indexExpr}]";
}
string result = $"{vectorName}.x";
for (int i = 1; i < 4; i++)
{
result = $"(({indexExpr}) == {i}) ? ({vectorName}.{GetSwizzleMask(i)}) : ({result})";
}
return $"({result})";
}
public static string GetConstantBufferName(int slot, string offsetExpr, ShaderStage stage, bool cbIndexable, bool indexElement)
{
return GetVec4Indexed(GetUbName(stage, slot, cbIndexable) + $"[{offsetExpr} >> 2]", offsetExpr + " & 3", indexElement);
}
public static string GetConstantBufferName(string slotExpr, string offsetExpr, ShaderStage stage, bool indexElement)
{
return GetVec4Indexed(GetUbName(stage, slotExpr) + $"[{offsetExpr} >> 2]", offsetExpr + " & 3", indexElement);
}
public static string GetUbName(ShaderStage stage, int slot, bool cbIndexable)
{
if (cbIndexable)
{
return GetUbName(stage, NumberFormatter.FormatInt(slot, AggregateType.S32));
}
return $"{GetShaderStagePrefix(stage)}_{DefaultNames.UniformNamePrefix}{slot}_{DefaultNames.UniformNameSuffix}";
}
private static string GetUbName(ShaderStage stage, string slotExpr)
{
return $"{GetShaderStagePrefix(stage)}_{DefaultNames.UniformNamePrefix}[{slotExpr}].{DefaultNames.DataName}";
}
public static string GetSamplerName(ShaderStage stage, AstTextureOperation texOp, string indexExpr)
{
return GetSamplerName(stage, texOp.CbufSlot, texOp.Handle, texOp.Type.HasFlag(SamplerType.Indexed), indexExpr);
@ -168,6 +117,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
switch (operation.StorageKind)
{
case StorageKind.ConstantBuffer:
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.");
}
if (!(operation.GetSource(1) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
{
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];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
return field.Type & AggregateType.ElementTypeMask;
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:

View File

@ -23,9 +23,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public int InputVertices { get; }
public Dictionary<int, Instruction> UniformBuffers { get; } = new Dictionary<int, Instruction>();
public Instruction SupportBuffer { get; set; }
public Instruction UniformBuffersArray { get; set; }
public Dictionary<int, Instruction> ConstantBuffers { get; } = new Dictionary<int, Instruction>();
public Instruction StorageBuffersArray { get; set; }
public Instruction LocalMemory { get; set; }
public Instruction SharedMemory { get; set; }
@ -38,6 +36,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
public Instruction CoordTemp { get; set; }
public StructuredFunction CurrentFunction { get; set; }
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> _funcArgs = new Dictionary<int, Instruction>();
@ -217,7 +216,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
IrOperandType.Argument => GetArgument(type, operand),
IrOperandType.Constant => GetConstant(type, operand),
IrOperandType.ConstantBuffer => GetConstantBuffer(type, operand),
IrOperandType.LocalVariable => GetLocal(type, operand),
IrOperandType.Undefined => GetUndefined(type),
_ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\".")
@ -274,31 +272,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
};
}
public Instruction GetConstantBuffer(AggregateType type, AstOperand operand)
{
var i1 = Constant(TypeS32(), 0);
var i2 = Constant(TypeS32(), operand.CbufOffset >> 2);
var i3 = Constant(TypeU32(), operand.CbufOffset & 3);
Instruction elemPointer;
if (UniformBuffersArray != null)
{
var ubVariable = UniformBuffersArray;
var i0 = Constant(TypeS32(), operand.CbufSlot);
elemPointer = AccessChain(TypePointer(StorageClass.Uniform, TypeFP32()), ubVariable, i0, i1, i2, i3);
}
else
{
var ubVariable = UniformBuffers[operand.CbufSlot];
elemPointer = AccessChain(TypePointer(StorageClass.Uniform, TypeFP32()), ubVariable, i1, i2, i3);
}
return BitcastIfNeeded(type, AggregateType.FP32, Load(TypeFP32(), elemPointer));
}
public Instruction GetLocalPointer(AstOperand local)
{
return _locals[local];

View File

@ -98,8 +98,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
DeclareLocalMemory(context, localMemorySize);
}
DeclareSupportBuffer(context);
DeclareUniformBuffers(context, context.Config.GetConstantBufferDescriptors());
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Config.GetStorageBufferDescriptors());
DeclareSamplers(context, context.Config.GetTextureDescriptors());
DeclareImages(context, context.Config.GetImageDescriptors());
@ -127,84 +126,63 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return variable;
}
private static void DeclareSupportBuffer(CodeGenContext context)
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
{
if (!context.Config.Stage.SupportsRenderScale() && !(context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable()))
HashSet<SpvInstruction> decoratedTypes = new HashSet<SpvInstruction>();
foreach (BufferDefinition buffer in buffers)
{
return;
}
int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4;
int alignmentMask = alignment - 1;
int offset = 0;
var isBgraArrayType = context.TypeArray(context.TypeU32(), context.Constant(context.TypeU32(), SupportBuffer.FragmentIsBgraCount));
var viewportInverseVectorType = context.TypeVector(context.TypeFP32(), 4);
var renderScaleArrayType = context.TypeArray(context.TypeFP32(), context.Constant(context.TypeU32(), SupportBuffer.RenderScaleMaxCount));
SpvInstruction[] structFieldTypes = new SpvInstruction[buffer.Type.Fields.Length];
int[] structFieldOffsets = new int[buffer.Type.Fields.Length];
context.Decorate(isBgraArrayType, Decoration.ArrayStride, (LiteralInteger)SupportBuffer.FieldSize);
context.Decorate(renderScaleArrayType, Decoration.ArrayStride, (LiteralInteger)SupportBuffer.FieldSize);
for (int fieldIndex = 0; fieldIndex < buffer.Type.Fields.Length; fieldIndex++)
{
StructureField field = buffer.Type.Fields[fieldIndex];
int fieldSize = (field.Type.GetSizeInBytes() + alignmentMask) & ~alignmentMask;
var supportBufferStructType = context.TypeStruct(false, context.TypeU32(), isBgraArrayType, viewportInverseVectorType, context.TypeS32(), renderScaleArrayType);
structFieldTypes[fieldIndex] = context.GetType(field.Type, field.ArrayLength);
structFieldOffsets[fieldIndex] = offset;
context.MemberDecorate(supportBufferStructType, 0, Decoration.Offset, (LiteralInteger)SupportBuffer.FragmentAlphaTestOffset);
context.MemberDecorate(supportBufferStructType, 1, Decoration.Offset, (LiteralInteger)SupportBuffer.FragmentIsBgraOffset);
context.MemberDecorate(supportBufferStructType, 2, Decoration.Offset, (LiteralInteger)SupportBuffer.ViewportInverseOffset);
context.MemberDecorate(supportBufferStructType, 3, Decoration.Offset, (LiteralInteger)SupportBuffer.FragmentRenderScaleCountOffset);
context.MemberDecorate(supportBufferStructType, 4, Decoration.Offset, (LiteralInteger)SupportBuffer.GraphicsRenderScaleOffset);
context.Decorate(supportBufferStructType, Decoration.Block);
if (field.Type.HasFlag(AggregateType.Array))
{
// We can't decorate the type more than once.
if (decoratedTypes.Add(structFieldTypes[fieldIndex]))
{
context.Decorate(structFieldTypes[fieldIndex], Decoration.ArrayStride, (LiteralInteger)fieldSize);
}
var supportBufferPointerType = context.TypePointer(StorageClass.Uniform, supportBufferStructType);
var supportBufferVariable = context.Variable(supportBufferPointerType, StorageClass.Uniform);
offset += fieldSize * field.ArrayLength;
}
else
{
offset += fieldSize;
}
}
context.Decorate(supportBufferVariable, Decoration.DescriptorSet, (LiteralInteger)0);
context.Decorate(supportBufferVariable, Decoration.Binding, (LiteralInteger)0);
var ubStructType = context.TypeStruct(false, structFieldTypes);
context.AddGlobalVariable(supportBufferVariable);
if (decoratedTypes.Add(ubStructType))
{
context.Decorate(ubStructType, Decoration.Block);
context.SupportBuffer = supportBufferVariable;
}
for (int fieldIndex = 0; fieldIndex < structFieldOffsets.Length; fieldIndex++)
{
context.MemberDecorate(ubStructType, fieldIndex, Decoration.Offset, (LiteralInteger)structFieldOffsets[fieldIndex]);
}
}
private static void DeclareUniformBuffers(CodeGenContext context, BufferDescriptor[] descriptors)
{
if (descriptors.Length == 0)
{
return;
}
uint ubSize = Constants.ConstantBufferSize / 16;
var ubArrayType = context.TypeArray(context.TypeVector(context.TypeFP32(), 4), context.Constant(context.TypeU32(), ubSize), true);
context.Decorate(ubArrayType, Decoration.ArrayStride, (LiteralInteger)16);
var ubStructType = context.TypeStruct(true, ubArrayType);
context.Decorate(ubStructType, Decoration.Block);
context.MemberDecorate(ubStructType, 0, Decoration.Offset, (LiteralInteger)0);
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing))
{
int count = descriptors.Max(x => x.Slot) + 1;
var ubStructArrayType = context.TypeArray(ubStructType, context.Constant(context.TypeU32(), count));
var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructArrayType);
var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructType);
var ubVariable = context.Variable(ubPointerType, StorageClass.Uniform);
context.Name(ubVariable, $"{GetStagePrefix(context.Config.Stage)}_u");
context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)0);
context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)context.Config.FirstConstantBufferBinding);
context.Name(ubVariable, buffer.Name);
context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)buffer.Set);
context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)buffer.Binding);
context.AddGlobalVariable(ubVariable);
context.UniformBuffersArray = ubVariable;
}
else
{
var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructType);
foreach (var descriptor in descriptors)
{
var ubVariable = context.Variable(ubPointerType, StorageClass.Uniform);
context.Name(ubVariable, $"{GetStagePrefix(context.Config.Stage)}_c{descriptor.Slot}");
context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)0);
context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)descriptor.Binding);
context.AddGlobalVariable(ubVariable);
context.UniformBuffers.Add(descriptor.Slot, ubVariable);
}
context.ConstantBuffers.Add(buffer.Binding, ubVariable);
}
}
@ -394,25 +372,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
foreach (var ioDefinition in info.IoDefinitions)
{
var ioVariable = ioDefinition.IoVariable;
// Those are actually from constant buffer, rather than being actual inputs or outputs,
// so we must ignore them here as they are declared as part of the support buffer.
// TODO: Delete this after we represent this properly on the IR (as a constant buffer rather than "input").
if (ioVariable == IoVariable.FragmentOutputIsBgra ||
ioVariable == IoVariable.SupportBlockRenderScale ||
ioVariable == IoVariable.SupportBlockViewInverse)
{
continue;
}
bool isOutput = ioDefinition.StorageKind.IsOutput();
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
PixelImap iq = PixelImap.Unused;
if (context.Config.Stage == ShaderStage.Fragment)
{
var ioVariable = ioDefinition.IoVariable;
if (ioVariable == IoVariable.UserDefined)
{
iq = context.Config.ImapTypes[ioDefinition.Location].GetFirstUsedType();
@ -429,6 +393,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
bool isOutput = ioDefinition.StorageKind.IsOutput();
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq);
}
}

View File

@ -4,7 +4,6 @@ using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using static Spv.Specification;
@ -98,7 +97,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.ImageStore, GenerateImageStore);
Add(Instruction.IsNan, GenerateIsNan);
Add(Instruction.Load, GenerateLoad);
Add(Instruction.LoadConstant, GenerateLoadConstant);
Add(Instruction.LoadLocal, GenerateLoadLocal);
Add(Instruction.LoadShared, GenerateLoadShared);
Add(Instruction.LoadStorage, GenerateLoadStorage);
@ -115,6 +113,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.MemoryBarrier, GenerateMemoryBarrier);
Add(Instruction.Minimum, GenerateMinimum);
Add(Instruction.MinimumU32, GenerateMinimumU32);
Add(Instruction.Modulo, GenerateModulo);
Add(Instruction.Multiply, GenerateMultiply);
Add(Instruction.MultiplyHighS32, GenerateMultiplyHighS32);
Add(Instruction.MultiplyHighU32, GenerateMultiplyHighU32);
@ -313,10 +312,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
for (int i = 0; i < args.Length; i++)
{
var operand = (AstOperand)operation.GetSource(i + 1);
var operand = operation.GetSource(i + 1);
if (i >= function.InArguments.Length)
{
args[i] = context.GetLocalPointer(operand);
args[i] = context.GetLocalPointer((AstOperand)operand);
}
else
{
@ -744,8 +744,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
pCoords = Src(AggregateType.S32);
}
pCoords = ScalingHelpers.ApplyScaling(context, texOp, pCoords, intCoords: true, isBindless, isIndexed, isArray, pCount);
(var imageType, var imageVariable) = context.Images[new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format)];
var image = context.Load(imageType, imageVariable);
@ -867,68 +865,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return GenerateLoadOrStore(context, operation, isStore: false);
}
private static OperationResult GenerateLoadConstant(CodeGenContext context, AstOperation operation)
{
var src1 = operation.GetSource(0);
var src2 = context.Get(AggregateType.S32, operation.GetSource(1));
var i1 = context.Constant(context.TypeS32(), 0);
var i2 = context.ShiftRightArithmetic(context.TypeS32(), src2, context.Constant(context.TypeS32(), 2));
var i3 = context.BitwiseAnd(context.TypeS32(), src2, context.Constant(context.TypeS32(), 3));
SpvInstruction value = null;
if (context.Config.GpuAccessor.QueryHostHasVectorIndexingBug())
{
// Test for each component individually.
for (int i = 0; i < 4; i++)
{
var component = context.Constant(context.TypeS32(), i);
SpvInstruction elemPointer;
if (context.UniformBuffersArray != null)
{
var ubVariable = context.UniformBuffersArray;
var i0 = context.Get(AggregateType.S32, src1);
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i0, i1, i2, component);
}
else
{
var ubVariable = context.UniformBuffers[((AstOperand)src1).Value];
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i1, i2, component);
}
SpvInstruction newValue = context.Load(context.TypeFP32(), elemPointer);
value = value != null ? context.Select(context.TypeFP32(), context.IEqual(context.TypeBool(), i3, component), newValue, value) : newValue;
}
}
else
{
SpvInstruction elemPointer;
if (context.UniformBuffersArray != null)
{
var ubVariable = context.UniformBuffersArray;
var i0 = context.Get(AggregateType.S32, src1);
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i0, i1, i2, i3);
}
else
{
var ubVariable = context.UniformBuffers[((AstOperand)src1).Value];
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i1, i2, i3);
}
value = context.Load(context.TypeFP32(), elemPointer);
}
return new OperationResult(AggregateType.FP32, value);
}
private static OperationResult GenerateLoadLocal(CodeGenContext context, AstOperation operation)
{
return GenerateLoadLocalOrShared(context, operation, StorageClass.Private, context.LocalMemory);
@ -1102,6 +1038,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return GenerateBinaryU32(context, operation, context.Delegates.GlslUMin);
}
private static OperationResult GenerateModulo(CodeGenContext context, AstOperation operation)
{
return GenerateBinary(context, operation, context.Delegates.FMod, null);
}
private static OperationResult GenerateMultiply(CodeGenContext context, AstOperation operation)
{
return GenerateBinary(context, operation, context.Delegates.FMul, context.Delegates.IMul);
@ -1163,7 +1104,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateReturn(CodeGenContext context, AstOperation operation)
{
context.Return();
if (operation.SourcesCount != 0)
{
context.ReturnValue(context.Get(context.CurrentFunction.ReturnType, operation.GetSource(0)));
}
else
{
context.Return();
}
return OperationResult.Invalid;
}
@ -1501,35 +1450,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
SpvInstruction ApplyBias(SpvInstruction vector, SpvInstruction image)
{
int gatherBiasPrecision = context.Config.GpuAccessor.QueryHostGatherBiasPrecision();
if (isGather && gatherBiasPrecision != 0)
{
// GPU requires texture gather to be slightly offset to match NVIDIA behaviour when point is exactly between two texels.
// Offset by the gather precision divided by 2 to correct for rounding.
var sizeType = pCount == 1 ? context.TypeS32() : context.TypeVector(context.TypeS32(), pCount);
var pVectorType = pCount == 1 ? context.TypeFP32() : context.TypeVector(context.TypeFP32(), pCount);
var bias = context.Constant(context.TypeFP32(), (float)(1 << (gatherBiasPrecision + 1)));
var biasVector = context.CompositeConstruct(pVectorType, Enumerable.Repeat(bias, pCount).ToArray());
var one = context.Constant(context.TypeFP32(), 1f);
var oneVector = context.CompositeConstruct(pVectorType, Enumerable.Repeat(one, pCount).ToArray());
var divisor = context.FMul(
pVectorType,
context.ConvertSToF(pVectorType, context.ImageQuerySize(sizeType, image)),
biasVector);
vector = context.FAdd(pVectorType, vector, context.FDiv(pVectorType, oneVector, divisor));
}
return vector;
}
SpvInstruction pCoords = AssemblePVector(pCount);
pCoords = ScalingHelpers.ApplyScaling(context, texOp, pCoords, intCoords, isBindless, isIndexed, isArray, pCount);
SpvInstruction AssembleDerivativesVector(int count)
{
@ -1623,7 +1544,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (hasLodBias)
{
lodBias = Src(AggregateType.FP32);
lodBias = Src(AggregateType.FP32);
}
if (!isGather && !intCoords && !isMultisample && !hasLodLevel && !hasDerivatives && context.Config.Stage != ShaderStage.Fragment)
{
// Implicit LOD is only valid on fragment.
// Use the LOD bias as explicit LOD if available.
lod = lodBias ?? context.Constant(context.TypeFP32(), 0f);
lodBias = null;
hasLodBias = false;
hasLodLevel = true;
}
SpvInstruction compIdx = null;
@ -1688,8 +1621,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
image = context.Image(imageType, image);
}
pCoords = ApplyBias(pCoords, image);
var operands = operandsList.ToArray();
SpvInstruction result;
@ -1805,11 +1736,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
result = context.CompositeExtract(context.TypeS32(), result, (SpvLiteralInteger)texOp.Index);
}
if (texOp.Index < 2 || (type & SamplerType.Mask) == SamplerType.Texture3D)
{
result = ScalingHelpers.ApplyUnscaling(context, texOp.WithType(type), result, isBindless, isIndexed);
}
return new OperationResult(AggregateType.S32, result);
}
}
@ -1978,12 +1904,32 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
StorageKind storageKind = operation.StorageKind;
SpvInstruction pointer;
StorageClass storageClass;
SpvInstruction baseObj;
AggregateType varType;
int srcIndex = 0;
switch (storageKind)
{
case StorageKind.ConstantBuffer:
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.");
}
if (!(operation.GetSource(srcIndex) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
storageClass = StorageClass.Uniform;
varType = field.Type & AggregateType.ElementTypeMask;
baseObj = context.ConstantBuffers[bindingIndex.Value];
break;
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
@ -2026,33 +1972,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
varType = context.Config.GetFragmentOutputColorType(location);
}
else if (ioVariable == IoVariable.FragmentOutputIsBgra)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeU32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 1), elemIndex);
varType = AggregateType.U32;
break;
}
else if (ioVariable == IoVariable.SupportBlockRenderScale)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 4), elemIndex);
varType = AggregateType.FP32;
break;
}
else if (ioVariable == IoVariable.SupportBlockViewInverse)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 2), elemIndex);
varType = AggregateType.FP32;
break;
}
else
{
(_, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
@ -2060,55 +1979,57 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
varType &= AggregateType.ElementTypeMask;
int inputsCount = (isStore ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
var storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
var ioDefinition = new IoDefinition(storageKind, ioVariable, location, component);
var dict = isPerPatch
? (isOutput ? context.OutputsPerPatch : context.InputsPerPatch)
: (isOutput ? context.Outputs : context.Inputs);
SpvInstruction baseObj = dict[ioDefinition];
SpvInstruction e0, e1, e2;
switch (inputsCount)
{
case 0:
pointer = baseObj;
break;
case 1:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0);
break;
case 2:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1);
break;
case 3:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e2 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1, e2);
break;
default:
var indexes = new SpvInstruction[inputsCount];
int index = 0;
for (; index < inputsCount; srcIndex++, index++)
{
indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex));
}
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes);
break;
}
baseObj = dict[ioDefinition];
break;
default:
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
}
int inputsCount = (isStore ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
SpvInstruction e0, e1, e2;
SpvInstruction pointer;
switch (inputsCount)
{
case 0:
pointer = baseObj;
break;
case 1:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0);
break;
case 2:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1);
break;
case 3:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e2 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1, e2);
break;
default:
var indexes = new SpvInstruction[inputsCount];
int index = 0;
for (; index < inputsCount; srcIndex++, index++)
{
indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex));
}
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes);
break;
}
if (isStore)
{
context.Store(pointer, context.Get(varType, operation.GetSource(srcIndex)));
@ -2324,7 +2245,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@ -2335,7 +2256,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@ -2395,7 +2316,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@ -2406,7 +2327,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}

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.SupportBuffer, 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.SupportBuffer, 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.SupportBuffer, 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.SupportBuffer, 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 GlslFMin;
public readonly FuncBinaryInstruction GlslSMin;
public readonly FuncBinaryInstruction FMod;
public readonly FuncBinaryInstruction FMul;
public readonly FuncBinaryInstruction IMul;
public readonly FuncBinaryInstruction FSub;
@ -174,6 +175,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
GlslSMax = context.GlslSMax;
GlslFMin = context.GlslFMin;
GlslSMin = context.GlslSMin;
FMod = context.FMod;
FMul = context.FMul;
IMul = context.IMul;
FSub = context.FSub;

View File

@ -144,10 +144,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static void Generate(CodeGenContext context, StructuredProgramInfo info, int funcIndex)
{
var function = info.Functions[funcIndex];
(_, var spvFunc) = context.GetFunction(funcIndex);
(var function, var spvFunc) = context.GetFunction(funcIndex);
context.CurrentFunction = function;
context.AddFunction(spvFunc);
context.StartFunction();

View File

@ -367,6 +367,15 @@ namespace Ryujinx.Graphics.Shader
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>
/// Queries the point size from the GPU state, used when it is not explicitly set on the shader.
/// </summary>

View File

@ -187,27 +187,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.Config.GpuAccessor.Log("Shader instruction Longjmp is not implemented.");
}
public static void P2rR(EmitterContext context)
{
InstP2rR op = context.GetOp<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)
{
InstPexit op = context.GetOp<InstPexit>();

View File

@ -160,7 +160,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
// FragCoord X/Y must be divided by the render target scale, if resolution scaling is active,
// because the shader code is not expecting scaled values.
res = context.FPDivide(res, context.Load(StorageKind.Input, IoVariable.SupportBlockRenderScale, null, Const(0)));
res = context.FPDivide(res, context.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.RenderScale), Const(0)));
}
else if (op.Imm10 == AttributeConsts.FrontFacing && context.Config.GpuAccessor.QueryHostHasFrontFacingBug())
{

View File

@ -1,6 +1,7 @@
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using System.Numerics;
using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
@ -80,7 +81,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand addr = context.IAdd(srcA, Const(Imm16ToSInt(op.CbufOffset)));
Operand wordOffset = context.ShiftRightU32(addr, Const(2));
Operand bitOffset = GetBitOffset(context, addr);
for (int index = 0; index < count; index++)
{
@ -92,11 +92,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
Operand offset = context.IAdd(wordOffset, Const(index));
Operand value = context.LoadConstant(slot, offset);
Operand value = EmitLoadConstant(context, slot, offset);
if (isSmallInt)
{
value = ExtractSmallInt(context, (LsSize)op.LsSize, bitOffset, value);
value = ExtractSmallInt(context, (LsSize)op.LsSize, GetBitOffset(context, addr), value);
}
context.Copy(Register(dest), value);
@ -154,6 +154,39 @@ namespace Ryujinx.Graphics.Shader.Instructions
EmitStore(context, MemoryRegion.Shared, op.LsSize, GetSrcReg(context, op.SrcA), op.Dest, Imm24ToSInt(op.Imm24));
}
private static Operand EmitLoadConstant(EmitterContext context, Operand slot, Operand offset)
{
Operand vecIndex = context.ShiftRightU32(offset, Const(2));
Operand elemIndex = context.BitwiseAnd(offset, Const(3));
if (slot.Type == OperandType.Constant)
{
int binding = context.Config.ResourceManager.GetConstantBufferBinding(slot.Value);
return context.Load(StorageKind.ConstantBuffer, binding, Const(0), vecIndex, elemIndex);
}
else
{
Operand value = Const(0);
uint cbUseMask = context.Config.GpuAccessor.QueryConstantBufferUse();
while (cbUseMask != 0)
{
int cbIndex = BitOperations.TrailingZeroCount(cbUseMask);
int binding = context.Config.ResourceManager.GetConstantBufferBinding(cbIndex);
Operand isCurrent = context.ICompareEqual(slot, Const(cbIndex));
Operand currentValue = context.Load(StorageKind.ConstantBuffer, binding, Const(0), vecIndex, elemIndex);
value = context.ConditionalSelect(isCurrent, currentValue, value);
cbUseMask &= ~(1u << cbIndex);
}
return value;
}
}
private static Operand EmitAtomicOp(
EmitterContext context,
StorageKind storageKind,

View File

@ -209,21 +209,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
return context.ICompareNotEqual(context.BitwiseAnd(value, Const(1 << bit)), Const(0));
}
if (ccpr)
int count = ccpr ? RegisterConsts.FlagsCount : RegisterConsts.PredsCount;
RegisterType type = ccpr ? RegisterType.Flag : RegisterType.Predicate;
int shift = (int)byteSel * 8;
for (int bit = 0; bit < count; bit++)
{
// TODO: Support Register to condition code flags copy.
context.Config.GpuAccessor.Log("R2P.CC not implemented.");
}
else
{
int shift = (int)byteSel * 8;
for (int bit = 0; bit < RegisterConsts.PredsCount; bit++)
{
Operand pred = Register(bit, RegisterType.Predicate);
Operand res = context.ConditionalSelect(Test(mask, bit), Test(value, bit + shift), pred);
context.Copy(pred, res);
}
Operand flag = Register(bit, type);
Operand res = context.ConditionalSelect(Test(mask, bit), Test(value, bit + shift), flag);
context.Copy(flag, res);
}
}

View File

@ -1,7 +1,6 @@
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper;
using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
@ -50,5 +49,68 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.Copy(Register(op.DestPred, RegisterType.Predicate), p0Res);
context.Copy(Register(op.DestPredInv, RegisterType.Predicate), p1Res);
}
public static void P2rC(EmitterContext context)
{
InstP2rC op = context.GetOp<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,7 +79,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
ImageAtomic,
IsNan,
Load,
LoadConstant,
LoadGlobal,
LoadLocal,
LoadShared,
@ -98,6 +97,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
MemoryBarrier,
Minimum,
MinimumU32,
Modulo,
Multiply,
MultiplyHighS32,
MultiplyHighU32,

View File

@ -15,7 +15,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
FragmentCoord,
FragmentOutputColor,
FragmentOutputDepth,
FragmentOutputIsBgra, // TODO: Remove and use constant buffer access.
FrontColorDiffuse,
FrontColorSpecular,
FrontFacing,
@ -34,8 +33,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
SubgroupLaneId,
SubgroupLeMask,
SubgroupLtMask,
SupportBlockViewInverse, // TODO: Remove and use constant buffer access.
SupportBlockRenderScale, // TODO: Remove and use constant buffer access.
TessellationCoord,
TessellationLevelInner,
TessellationLevelOuter,

View File

@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public Instruction Inst { get; private set; }
public StorageKind StorageKind { get; }
public bool ForcePrecise { get; set; }
private Operand[] _dests;
public Operand Dest

View File

@ -4,10 +4,6 @@
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Remove="CodeGen\Glsl\HelperFunctions\TexelFetchScale_vp.glsl" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Spv.Generator\Spv.Generator.csproj" />
@ -25,9 +21,6 @@
<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\TexelFetchScale_vp.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\TexelFetchScale_fp.glsl" />
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\TexelFetchScale_cp.glsl" />
</ItemGroup>
</Project>

View File

@ -15,9 +15,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public int Value { get; }
public int CbufSlot { get; }
public int CbufOffset { get; }
private AstOperand()
{
Defs = new HashSet<IAstNode>();
@ -29,16 +26,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public AstOperand(Operand operand) : this()
{
Type = operand.Type;
if (Type == OperandType.ConstantBuffer)
{
CbufSlot = operand.GetCbufSlot();
CbufOffset = operand.GetCbufOffset();
}
else
{
Value = operand.Value;
}
Value = operand.Value;
}
public AstOperand(OperandType type, int value = 0) : this()

View File

@ -10,6 +10,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
public Instruction Inst { get; }
public StorageKind StorageKind { get; }
public bool ForcePrecise { get; }
public int Index { get; }
@ -17,10 +18,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public int SourcesCount => _sources.Length;
public AstOperation(Instruction inst, StorageKind storageKind, IAstNode[] sources, int sourcesCount)
public AstOperation(Instruction inst, StorageKind storageKind, bool forcePrecise, IAstNode[] sources, int sourcesCount)
{
Inst = inst;
StorageKind = storageKind;
ForcePrecise = forcePrecise;
_sources = sources;
for (int index = 0; index < sources.Length; index++)
@ -38,12 +40,18 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Index = 0;
}
public AstOperation(Instruction inst, StorageKind storageKind, int index, IAstNode[] sources, int sourcesCount) : this(inst, storageKind, sources, sourcesCount)
public AstOperation(
Instruction inst,
StorageKind storageKind,
bool forcePrecise,
int index,
IAstNode[] sources,
int sourcesCount) : this(inst, storageKind, forcePrecise, sources, sourcesCount)
{
Index = index;
}
public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, StorageKind.None, sources, sources.Length)
public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, StorageKind.None, false, sources, sources.Length)
{
}

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