Compare commits

..

113 Commits

Author SHA1 Message Date
18df25f66f nuget: bump the avalonia group with 2 updates (#6505)
Bumps the avalonia group with 2 updates: [Avalonia.Svg](https://github.com/wieslawsoltes/Svg.Skia) and [Avalonia.Svg.Skia](https://github.com/wieslawsoltes/Svg.Skia).


Updates `Avalonia.Svg` from 11.0.0.14 to 11.0.0.16
- [Release notes](https://github.com/wieslawsoltes/Svg.Skia/releases)
- [Changelog](https://github.com/wieslawsoltes/Svg.Skia/blob/master/CHANGELOG.md)
- [Commits](https://github.com/wieslawsoltes/Svg.Skia/commits)

Updates `Avalonia.Svg.Skia` from 11.0.0.14 to 11.0.0.16
- [Release notes](https://github.com/wieslawsoltes/Svg.Skia/releases)
- [Changelog](https://github.com/wieslawsoltes/Svg.Skia/blob/master/CHANGELOG.md)
- [Commits](https://github.com/wieslawsoltes/Svg.Skia/commits)

---
updated-dependencies:
- dependency-name: Avalonia.Svg
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
- dependency-name: Avalonia.Svg.Skia
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-16 20:02:18 +01:00
e19e7622a3 chore: remove repetitive words (#6500)
Signed-off-by: standstaff <zhengxingru@yeah.net>
2024-03-16 19:49:54 +01:00
26026d1357 Fix Title Update Manager not refreshing app list (#6507) 2024-03-16 19:46:03 +01:00
24068b023c discord: Update ApplicationID (#6513) 2024-03-16 19:41:38 +01:00
1217a8e69b GPU: Rebind RTs if scale changes when binding textures (#6493)
This fixes a longstanding issue with resolution scale that could result in flickering graphics, typically the first frame something is drawn, or on camera cuts in cutscenes.

The root cause of the issue is that texture scale can be changed when binding textures or images. This typically happens because a texture becomes a view of a larger texture, such as a 400x225 texture becoming a view of a 800x450 texture with two levels. If the 400x225 texture is bound as a render target and has state [1x Undesired], but the storage texture is [2x Scaled], the render target texture's scale is changed to [2x Scaled] to match its new storage. This means the scale changed after the render target state was processed...

This can cause a number of issues. When render target state is processed, texture scales are examined and potentially changed so that they are all the same value. If one texture is scaled, all textures must be. If one texture is blacklisted from scaling, all of them must be. This results in a single resolution scale value being assigned to the TextureManager, which also scales the scissor and viewport values.

If the scale is chosen as 1x, and a later texture binding changes one of the textures to be 2x, the scale in TextureManager no longer matches all of the bound textures. What's worse, the scales in these textures could mismatch entirely. This typically results in the support buffer scale, viewport and scissor being wrong for at least one of the bound render targets.

This PR fixes the issue by re-evaluating render target state if any scale mismatches the expected scale after texture bindings happen. This can actually cause scale to change again, so it must loop back to perform texture bindings again. This can happen as many times as it needs to, but I don't expect it to happen more than once. Problematic bindings will just result in a blacklist, which will propagate to other bound targets.
2024-03-14 19:59:09 -03:00
732db7581f Consider Polygon as unsupported is triangle fans are unsupported on Vulkan (#6490) 2024-03-14 19:46:57 -03:00
fdd3263e31 Separate guest/host tracking + unaligned protection (#6486)
* WIP: Separate guest/host tracking + unaligned protection

Allow memory manager to define support for single byte guest tracking

* Formatting

* Improve docs

* Properly handle cases where the address space bits are too low

* Address feedback
2024-03-14 19:38:27 -03:00
ce607db944 Ava UI: Update Ava (#6430)
* Update Ava

* Newline
2024-03-14 02:29:13 +01:00
6b4ee82e5d infra: Fix updater for old Ava users (#6441)
* Add binaries with both names to release archives

* Add migration code for the new filename

* Add Ryujinx.Ava to all win/linux releases for a while
2024-03-13 23:26:35 +01:00
e2a655f1a4 Update AutoDeleteCache.cs (#6471)
Increase the texture cache limit from 512 MB to 1 GB.
2024-03-13 17:39:39 -03:00
d9a18919b0 Fix geometry shader passthrough issue (#6462)
* Fix geometry shader passthrough issue (Diagnosed by gdkchan)

* Fix whitespace formatting

* Fix whitespace formatting

* Bump shader cache version

* Don't apply PassthroughNV decorations to output geometry shader variables
2024-03-13 17:26:19 -03:00
8354434a37 Passthrough mouse for win32 (#6450)
* passthrough mouse for win32

* remove unused enums
2024-03-11 21:21:39 -03:00
5a900f38c5 Fix lost copy and swap problem on shader SSA deconstruction (#6455)
* Fix lost copy on shader SSA deconstruction

* Shader cache version bump
2024-03-10 21:16:40 -03:00
a3a63d4394 Refactor memory managers to a common base class, consolidate Read() method logic (#6360)
* - add new abstract class `VirtualMemoryManagerBase`
- rename `MemoryManagerBase` to `VirtualMemoryManagerRefCountedBase` and derive from `VirtualMemoryManagerBase`
- change `AddressSpaceManager`, `HvMemoryManager`, `MemoryManager`, and `MemoryManagerHostMapped` to implement abstract members and use the inherited `void VirtualMemoryManagerBase.Read(TVirtual va, Span<byte> data)` implementation.

* move property `AddressSpaceSize` up by the other properties
2024-03-09 21:01:51 -03:00
3924bd1a43 Update dependencies from SixLabors to the latest version before the license change (#6440)
* nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3 (#3976)

* nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3

Bumps [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) from 1.0.4 to 2.1.3.
- [Release notes](https://github.com/SixLabors/ImageSharp/releases)
- [Commits](https://github.com/SixLabors/ImageSharp/compare/v1.0.4...v2.1.3)

---
updated-dependencies:
- dependency-name: SixLabors.ImageSharp
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update for 2.x changes

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mary <mary@mary.zone>

* Update SixLabors.ImageSharp to 2.1.7

This is the latest version we can update to without the license change.

* Update SixLabors.ImageSharp.Drawing to v1.0.0

This is the latest version we can update to without the license change.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mary <mary@mary.zone>
2024-03-08 13:16:32 -03:00
50458b2472 LightningJit: Disable some cache ops and CTR_EL0 access on Windows Arm (#6326)
* LightningJit: Disable some cache ops and CTR_EL0 access on Windows Arm

* Format whitespace

* Delete unused code

* Fix typo

Co-authored-by: riperiperi <rhy3756547@hotmail.com>

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2024-03-07 20:55:54 -03:00
dda0f26067 UI: Update minimum window size to 800x500 (#6425) 2024-03-07 20:38:56 -03:00
4e1a60328e Add title of game to screenshot text (#6266)
* Add sanitize method

* Add app name to screenshot text output

* Add app name to screenshot text
2024-03-07 22:49:57 +00:00
2505a1abcd misc: Remove myself from reviews
I have been mostly inactive on the project for the past year and a half
apart from handling CI and reviews because of a lack of motivation and
time.
2024-03-05 17:54:35 +01:00
bc4d99a078 ci: try to fix toctou on release creation
Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-03-02 12:58:03 +01:00
ec6cb0abb4 infra: Make Avalonia the default UI (#6375)
* misc: Move Ryujinx project to Ryujinx.Gtk3

This breaks release CI for now but that's fine.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* misc: Move Ryujinx.Ava project to Ryujinx

This breaks CI for now, but it's fine.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* infra: Make Avalonia the default UI

Should fix CI after the previous changes.

GTK3 isn't build by the release job anymore, only by PR CI.

This also ensure that the test-ava update package is still generated to
allow update from the old testing channel.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* Fix missing copy in create_app_bundle.sh

Signed-off-by: Mary Guillemard <mary@mary.zone>

* Fix syntax error

Signed-off-by: Mary Guillemard <mary@mary.zone>

---------

Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-03-02 12:51:05 +01:00
53b5985da6 Avalonia: only enable gamescope workaround under it (#6374)
Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-03-02 11:16:46 +01:00
87f238be60 Change packed aliasing formats to UInt (#6358) 2024-02-24 19:02:20 -03:00
4d0dbbfae2 nuget: bump System.Drawing.Common from 8.0.1 to 8.0.2 (#6308)
Bumps [System.Drawing.Common](https://github.com/dotnet/winforms) from 8.0.1 to 8.0.2.
- [Release notes](https://github.com/dotnet/winforms/releases)
- [Changelog](https://github.com/dotnet/winforms/blob/main/docs/release-activity.md)
- [Commits](https://github.com/dotnet/winforms/compare/v8.0.1...v8.0.2)

---
updated-dependencies:
- dependency-name: System.Drawing.Common
  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>
2024-02-24 01:17:24 +01:00
08384ee5a8 IPC code gen improvements (#6352)
* HipcGenerator: skip do-nothing call to MemoryMarshal.Cast<byte, byte>() in generated code

* HipcGenerator: fix method name typos

* HipcGenerator: make generated methods use stackalloc for `isBufferMapAlias` bool array

* HipcGenerator: make generated GetCommandHandlers() method return a FrozenDictionary<int, CommandHandler>

* HipcGenerator: return `FrozenDictionary<,>.Empty` when there are no command implementations, otherwise create `FrozenDictionary` from a `IEnumerable<KeyValuePair<,>>`` instead of a `Dictionary<,>``
2024-02-23 21:12:24 -03:00
d4d0a48bfe Migrate Audio service to new IPC (#6285)
* Migrate audren to new IPC

* Migrate audout

* Migrate audin

* Migrate hwopus

* Bye bye old audio service

* Switch volume control to IHardwareDeviceDriver

* Somewhat unrelated changes

* Remove Concentus reference from HLE

* Implement OpenAudioRendererForManualExecution

* Remove SetVolume/GetVolume methods that are not necessary

* Remove SetVolume/GetVolume methods that are not necessary (2)

* Fix incorrect volume update

* PR feedback

* PR feedback

* Stub audrec

* Init outParameter

* Make FinalOutputRecorderParameter/Internal readonly

* Make FinalOutputRecorder IDisposable

* Fix HardwareOpusDecoderManager parameter buffers

* Opus work buffer size and error handling improvements

* Add AudioInProtocolName enum

* Fix potential divisions by zero
2024-02-22 16:58:33 -03:00
57d8afd0c9 OpenGL: Mask out all color outputs with no fragment shader (#6341)
* OpenGL: Mask out all color outputs with no fragment shader

This appears to match Vulkan's behaviour, which is needed for stencil shadows in Penny's Big Breakaway. It's far from the only issue, you can try the Full Bindless PR if you want to see it in a more intact state.

* Remove unused member
2024-02-22 18:43:19 +01:00
c43fb92bbf Ensure service init runs after Horizon constructor (#6342) 2024-02-22 13:55:29 -03:00
167f50bbcd Implement virtual buffer dependencies (#6190)
* Implement virtual buffer copies

* Introduce TranslateAndCreateMultiBuffersPhysicalOnly, use it for copy and clear

* Rename VirtualBufferCache to VirtualRangeCache

* Fix potential issue where virtual range could exist in the cache, without a physical buffer

* Fix bug that could cause copy with negative size on CopyToDependantVirtualBuffer

* Remove virtual copy back for SyncAction

* GetData XML docs

* Make field readonly

* Fix virtual buffer modification tracking

* Remove CopyFromDependantVirtualBuffers from ExternalFlush

* Move things around a little to avoid perf impact

- Inline null check for CopyFromDependantVirtualBuffers
- Remove extra method call for SynchronizeMemoryWithVirtualCopyBack, prefer calling CopyFromDependantVirtualBuffers separately

* Fix up XML doc

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2024-02-22 11:03:07 -03:00
ba91f5d401 Vulkan: Properly reset barrier batch when splitting due to mismatching flags (#6345)
Forgot to set the end variable here. Should stop it from crashing when this path is taken.
2024-02-22 10:43:22 +01:00
79f6c18a9b Vulkan: Disable push descriptors on older NVIDIA GPUs (#6340)
Disables push descriptors on older NVIDIA GPUs (10xx and below), since it is clearly broken beyond comprehension. The existing workaround wasn't good enough and a more thorough one will probably cost more performance than the feature gains. The workaround has been removed.

Fixes #6331.
2024-02-21 23:52:13 -03:00
4f63782bac Vulkan: Fix barrier batching past limit (#6339)
If more than 16 barriers were queued at one time, the _queuedBarrierCount would no longer match the number of remaining barriers, because when breaking out of the loop consuming them it deleted all barriers, not just the 16 that were consumed.

Should fix freezes that started occurring with #6240. Fixes issue #6338.
2024-02-21 23:41:08 -03:00
6f5fcb7970 Avalonia UI: Update English tooltips (#6305)
* update English tooltips

* missed a dot
2024-02-19 23:27:15 +01:00
6ef8946169 Avalonia: Fix gamescope once and for all (#6301)
Enable input focus proxy, makes WM_TAKE_FOCUS capability to be exposed.

This fix menu on gamescope, for real this time....

Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-02-19 21:18:46 +01:00
42340fc743 LightningJit: Add a limit on the number of instructions per function for Arm64 (#6328) 2024-02-17 17:30:54 -03:00
103e7cb021 hid: Stub SetTouchScreenResolution (#6322)
* hid: Implement SetTouchScreenResolution

* Fix Tomb Raider I-III Remastered from crashing without enabling Ignore Missing Services

* PR Feedback: Update Comments
2024-02-17 14:49:50 -03:00
31ed061bea Vulkan: Improve texture barrier usage, timing and batching (#6240)
* WIP barrier batch

* Add store op to image usage barrier

* Dispose the barrier batch

* Fix encoding?

* Handle read and write on the load op barrier.

Load op consumes read accesses but does not add one, as the only other operation that can read is another load.

* Simplify null check

* Insert barriers on program change in case stale bindings are reintroduced

* Not sure how I messed this one up

* Improve location of bindings barrier update

This is also important for emergency deferred clear

* Update src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs

Co-authored-by: Mary Guillemard <thog@protonmail.com>

---------

Co-authored-by: Mary Guillemard <thog@protonmail.com>
2024-02-17 00:21:37 -03:00
4218311e6a Vulkan: Use push descriptors for uniform bindings when possible (#6154)
* Fix Push Descriptors

* Use push descriptor templates

* Use reserved bindings

* Formatting

* Disable when using MVK

("my heart will go on" starts playing as thousands of mac users shed a tear in unison)

* Introduce limit on push descriptor binding number

The bitmask used for updating push descriptors is ulong, so only 64 bindings can be tracked for now.

* Address feedback

* Fix logic for binding rejection

Should only offset limit when reserved bindings are less than the requested one.

* Workaround pascal and older nv bug

* Add GPU number detection for nvidia

* Only do workaround if it's valid to do so.
2024-02-16 21:41:30 -03:00
e37735ed26 Implement X8Z24 texture format (#6315) 2024-02-15 19:06:26 -03:00
74a18b7c18 Fix PermissionLocked check on UnmapProcessCodeMemory (#6314) 2024-02-15 16:16:01 -03:00
74fe814329 Remove Vulkan SubgroupSizeControl enablement code (#6317) 2024-02-15 16:04:30 -03:00
d1a093e5ca Stub VSMS related ioctls (#6313)
* Stub VSMS related ioctls

* Clean up usings
2024-02-15 19:48:22 +01:00
dfb14a5607 Updaters: Fix ARM Linux Updater (#6316)
* Remove Arch Checks

* Fix ARM Linux updater
2024-02-15 10:41:43 +01:00
jcm
904a5ffcb4 Handle exceptions when checking user data directory for symlink (#6304)
Co-authored-by: jcm <butt@butts.com>
2024-02-12 00:10:21 +01:00
jcm
946633276b macOS: Stop storing user data in Documents for some users; fix symlinks (#6241)
* macOS: Stop storing user data in Documents for some users; fix symlinks

* Use SupportedOSPlatform tag, catch exceptions, log warning instead of error

* Provide best path hints to user if symlink fixup fails

---------

Co-authored-by: jcm <butt@butts.com>
2024-02-11 19:04:39 +01:00
baf94e0e3e infra: Force add linux-x64 apphost in flathub nuget source (#6302)
Required when building on the arm64 runner.

Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-02-11 16:45:58 +01:00
cf6201a4a6 infra: Restore Nuget packages for linux-arm64 for Flatpak
Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-02-11 13:21:54 +01:00
18909195d1 Old buttons (#6237) 2024-02-11 03:12:43 +01:00
f06d22d6f0 Infra: Capitalisation Consistency (#6296)
* Rename Ryujinx.UI.Common

* Rename Ryujinx.UI.LocaleGenerator

* Update in Files

AboutWindow

* Configuration State

* Rename projects

* Ryujinx/UI

* Fix build

* Main remaining inconsistencies

* HLE.UI Namespace

* HLE.UI Files

* Namespace

* Ryujinx.UI.Common.Configuration.UI

* Ryujinx.UI.Common,Configuration.UI Files

* More instances
2024-02-11 03:09:18 +01:00
jcm
84d6e8d121 Standardize logging locations across desktop platforms (#6238)
* Standardize logging locations across desktop platforms

* Return null instead of empty literal on exceptions

* Remove LogDirectoryPath from LoggerModule

* Catch exception when creating DirectoryInfo in FileLogTarget

* Remove redundant log path vars, handle exception better, add null check

* Address styling issues

* Remove extra newline, quote file path in log, move directory check to OpenHelper

* Add GetOrCreateLogsDir to get/create log directory during runtime

* misc format changes

* Update src/Ryujinx.Common/Configuration/AppDataManager.cs

---------

Co-authored-by: jcm <butt@butts.com>
Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2024-02-11 02:17:19 +01:00
95c4912d58 Linux: Reorder available executables in Ryujinx.sh (#6171)
* Avoid Ryujinx.Headless.SDL2 as a last resort in Ryujinx.desktop when you
  have more than one executable installed.
2024-02-11 00:57:23 +01:00
356a75af0b Remove ReflectionBinding in Mod Manager (#6280) 2024-02-11 00:52:11 +01:00
4ae9921063 Update Avalonia About Window like requested in PR #6267 (#6278)
* Update About Window like requested in PR #6267

* Feedback

* Apply suggestions from code review

Co-authored-by: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com>

* Fix indents

---------

Co-authored-by: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com>
2024-02-11 00:45:14 +01:00
6a8ac389e5 Fix mip offset/size for full 3D texture upload on Vulkan (#6294) 2024-02-11 00:41:17 +01:00
8dd1eb333c Add missing RID exclusions for linux-arm64 (#6298)
* Add missing RID exclusions for linux-arm64

Signed-off-by: Mary Guillemard <mary@mary.zone>

* Remove libsoundio.so from linux-arm64 deployment

This is a x86_64 library.

Signed-off-by: Mary Guillemard <mary@mary.zone>

---------

Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-02-10 22:49:32 +01:00
7dc3a62c14 ci: Enable Linux ARM64 on build and release (#6291)
* ci: Enable Linux ARM64 on build and release

Signed-off-by: Mary <mary@mary.zone>

* Address gdkchan comment

Signed-off-by: Mary <mary@mary.zone>

---------

Signed-off-by: Mary <mary@mary.zone>
2024-02-10 22:05:46 +01:00
e59dba42ef Set PointSize in shader on OpenGL (#6292)
Previously we were only doing it for Vulkan, but it turns out that
not setting it when PROGRAM_POINT_SIZE is set is considered UB
on OpenGL Core.

Signed-off-by: Mary <mary@mary.zone>
2024-02-10 20:27:17 +01:00
bd6937ae5c Make IOpenGLContext.HasContext context dependent (#6290)
This makes IOpenGLContext.HasContext not static and be implementable.

By doing this, we can support more than WGL and WGL.

This also allows the SDL2 headless version to run under Wayland.

Signed-off-by: Mary <mary@mary.zone>
2024-02-10 20:13:10 +01:00
jcm
b82e789d4f Load custom SDL mappings from application data folder (#6295)
Co-authored-by: jcm <butt@butts.com>
2024-02-10 19:41:02 +01:00
4a6724622e Force CPU copy for non-identity DMA remap (#6293) 2024-02-10 15:38:58 -03:00
0c73eba3db misc: Update to Ryujinx.Graphics.Nvdec.Dependencies 5.0.3-build14 (#6279)
Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-02-10 11:23:44 +01:00
a082e14ede Revert "Bump Ava (#6271)"
This reverts commit dfc0819e72.

X popup position broke entirely (my fault oops), waiting for next release with a fix
(see https://github.com/AvaloniaUI/Avalonia/pull/14551)
2024-02-08 22:14:56 +01:00
d29da11d5f Remove SDC (#6275) 2024-02-08 20:36:59 +01:00
ea07328aea LightningJit: Reduce stack usage for Arm32 code (#6245)
* Write/read guest state to context for sync points, stop reserving stack for them

* Fix UsedGprsMask not being updated when allocating with preferencing

* POP should be also considered a return
2024-02-08 20:17:47 +01:00
a0b3d82ee0 Remove Vic Reference to Host1x (#6277) 2024-02-08 20:01:03 +01:00
609de33b0b Implement BGR10A2 render target format (#6273) 2024-02-08 19:52:38 +01:00
dfc0819e72 Bump Ava (#6271) 2024-02-08 19:45:18 +01:00
d4803356bb nuget: bump Microsoft.NET.Test.Sdk from 17.8.0 to 17.9.0 (#6265)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.8.0 to 17.9.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.8.0...v17.9.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>
2024-02-08 19:38:51 +01:00
459efd0db7 Replace Flex Panels in favor of Wrap Panels for Avalonia (#6267)
* Switch from using Flex panel to a Wrap panel for Grid view. This allows keyboard navigation.

* Stop using Flex panel in favor of Avalonia Wrap Panel.
2024-02-08 19:34:48 +01:00
8bb7a3fc97 Clamp vertex buffer size to mapped size if too high (#6272)
* Clamp vertex buffer size to mapped size if too high

* Update comment
2024-02-08 18:27:12 +01:00
628d092fc6 chore: Update Ryujinx.SDL2-CS to 2.30.0 (#6261)
Also add linux-arm64 support.

Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-02-07 22:43:44 +01:00
6c90d50c8e Redact usernames from logs (#6255)
* Redact usernames from logs

* Changed internal vars to private and applied naming rules

* Use Directory.GetParent() instead of DirectoryInfo

* Update src/Ryujinx.Common/Logging/Logger.cs

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2024-02-06 23:29:50 +01:00
d56bab1e24 AccountService: Cache token data (#6260)
* AccountService: Cache token data

This method appears to indicate that the token returned should be cached. I've made it so that it generates a token that lasts until its expiration time, and reuses it on subsequent calls.

* Private naming convention
2024-02-06 23:11:20 +01:00
a37e2d6e44 Resolve an issue where changes to the main window's positioning could cause the application to crash if a modal was dismissed beforehand. (#6223) 2024-02-06 18:05:32 +01:00
25123232bd nuget: bump SPB from 0.0.4-build28 to 0.0.4-build32 (#6235)
Bumps [SPB](https://github.com/marysaka/SPB) from 0.0.4-build28 to 0.0.4-build32.
- [Release notes](https://github.com/marysaka/SPB/releases)
- [Commits](https://github.com/marysaka/SPB/commits)

---
updated-dependencies:
- dependency-name: SPB
  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>
2024-02-05 15:39:45 +01:00
8927e0669f Revert change to skip flush when range size is 0 (#6254) 2024-02-04 18:12:12 -03:00
bbed3b9926 Fix depth compare value for TLD4S shader instruction with offset (#6253)
* Fix depth compare value for TLD4S shader instruction with offset

* Shader cache version bump
2024-02-04 20:58:17 +01:00
24c8b0edc0 Remove component operand for texture gather with depth compare (#6247) 2024-02-04 11:10:45 +01:00
e5066449a5 Limit remote closed session removal to SM service (#6248) 2024-02-03 19:40:09 +01:00
d704bcd93b Ensure SM service won't listen to closed sessions (#6246)
* Ensure SM service won't listen to closed sessions

* PR feedback
2024-02-02 20:56:51 -03:00
c94f0fbb83 Vulkan: Add Render Pass / Framebuffer Cache (#6182)
* Vulkan: Add Render Pass / Framebuffer Cache

Cache is owned by each texture view.

- Window's way of getting framebuffer cache for swapchain images is really messy - it creates a TextureView out of just a vk image view, with invalid info and no storage.

* Clear up limited use of alternate TextureView constructor

* Formatting and messages

* More formatting and messages

I apologize for `_colorsCanonical[index]?.Storage?.InsertReadToWriteBarrier`, the compiler made me do it

* Self review, change GetFramebuffer to GetPassAndFramebuffer

* Avoid allocations on Remove for HashTableSlim

* Member can be readonly

* Generate texture create info for swapchain images

* Improve hashcode

* Remove format, samples, size and isDepthStencil when possible

Tested in a number of games, seems fine.

* Removed load op barriers

These can be introduced later.

* Reintroduce UpdateModifications

Technically meant to be replaced by load op stuff.
2024-01-31 23:49:50 +01:00
d1b30fbe08 nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.2.0 to 7.3.0 (#6227)
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.2.0 to 7.3.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/7.2.0...7.3.0)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  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>
2024-01-31 23:46:48 +01:00
4505a7f162 Fix opening the wrong log directory (#6220) 2024-01-30 17:52:45 +01:00
ccbbaddbcb Fix exception when trying to read output pointer buffer size (#6221)
* Fix exception when trying to read output pointer buffer size

* Better name
2024-01-29 21:19:39 -03:00
8bf102d2cd Cpu: Implement Vpadal and Vrintr instructions (#6185)
* Cpu: Implement Vpadal and Vrintr instructions

This PR superseed last instructions left in #2242.
Since I'm not a CPU guy I've just ported the code and nothing more.
Please be precise during review if there are some changes to be done.

It should fixes #1781

Co-Authored-By: Piyachet Kanda <piyachetk@gmail.com>

* Addresses gdkchan's feedback

* Addresses gdkchan's feedback 2

* Apply suggestions from code review

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

* another fix

* Update InstEmitSimdHelper32.cs

* Correct fix

* Addresses gdkchan's feedback

* Update CpuTestSimdCvt32.cs

---------

Co-authored-by: Piyachet Kanda <piyachetk@gmail.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2024-01-30 00:51:05 +01:00
2adf031830 deps: Update Avalonia.Svg (#6218)
Updates `Avalonia.Svg` from 11.0.0.10 to 11.0.0.13
Updates `Avalonia.Svg.Skia` from 11.0.0.10 to 11.0.0.13

And reflect update changes as dependabot can't do it.

Superseed #6209
2024-01-29 23:22:42 +01:00
bb4a28b525 Ava UI: Mod Manager Fixes (Again) (#6187)
* Fix typo + Fix deleting from old dir

* Avoid double enumeration

* Break when parentDir is found

* Fix deleting non subdirectory mods

* Typo
2024-01-29 23:14:19 +01:00
a8fbcdae9f UI: Clarify Create Application Shortcut tooltip text (#6217) 2024-01-29 23:08:24 +01:00
4e81ab4229 Avalonia: Fix dialog issues caused by 1.1.1105 (#6211)
* Set _contentDialogOverlayWindow to null

* Make CheckLaunchState async
2024-01-29 22:57:20 +01:00
4117c13377 Migrate friends service to new IPC (#6174)
* Migrate friends service to new IPC

* Add a note that the pointer buffer size and domain counts are wrong

* Wrong length

* Format whitespace

* PR feedback

* Fill in structs from PR feedback

* Missed that one

* Somehow forgot to save that one

* Fill in enums from PR review

* Language enum, NotificationTime

* Format whitespace

* Fix the warning
2024-01-29 22:45:40 +01:00
20a392ad55 Remove events that trigger from a forked repository (#6213)
[skip ci]
2024-01-29 20:10:29 +01:00
70fcba39de Make config filename changable for releases & Log to Ryujinx directory if application directory is not writable (#4707)
* Remove GetBaseApplicationDirectory() & Move logs directory to user base path

We should assume the application directory might be write-protected.

* Use Ryujinx.sh in Ryujinx.desktop

This desktop file isn't really used right now,
so this changes effectively nothing.

* Use properties in ReleaseInformation.cs and add ConfigName property

* Configure config filename in Github workflows

* Add a separate config step for macOS

Because they use BSD sed instead of GNU sed

* Keep log directory at the old location for dev environments

* Add FileSystemUtils since Directory.Move() doesn't work across filesystems

Steal CopyDirectory code from https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories

* Fix "Open Logs folder" button pointing to the wrong directory

* Add execute permissions to Ryujinx.sh

* Fix missing newlines

* AppDataManager: Use FileSystemUtils.MoveDirectory()

* Make dotnet format happy

* Add a fallback for the logging directory
2024-01-29 19:58:18 +01:00
7795b662a9 Mod: Do LayeredFs loading Parallel to improve speed (#6180)
* Mod: Do LayeredFs loading Parallel to improve speed

This fixes and superseed #5672 due to inactivity, nothing more.
(See original PR for description)

Testing are welcome.

Close #5661

* Addresses gdkchan's feedback

* commit to test mako change

* Revert "commit to test mako change"

This reverts commit 8b0caa8a21.
2024-01-29 16:32:34 +01:00
30bdc4544e Fix NRE when calling GetSockName before Bind (#6206) 2024-01-29 16:28:32 +01:00
f6475cca17 infra: Reformat README.md & add new generic Mako workflow (#5791)
* Adjust workflow paths to exclude all markdown files

* editorconfig: Add default charset and adjust indention for a few file types

* Reformat README.md and add a link to our documentation

* Add generic Mako workflow and remove old Mako steps

* editorconfig: Move charset change to a different PR

* Update compatibility stats

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

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2024-01-27 20:50:28 +01:00
0335c52254 nuget: bump NetCoreServer from 7.0.0 to 8.0.7 (#6119)
Bumps [NetCoreServer](https://github.com/chronoxor/NetCoreServer) from 7.0.0 to 8.0.7.
- [Release notes](https://github.com/chronoxor/NetCoreServer/releases)
- [Commits](https://github.com/chronoxor/NetCoreServer/commits)

---
updated-dependencies:
- dependency-name: NetCoreServer
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 02:02:12 +01:00
b8d992e5a7 Allow skipping draws with broken pipeline variants on Vulkan (#5807)
* Allow skipping draws with broken pipeline variants on Vulkan

* Move IsLinked check to CreatePipeline

* Restore throw on error behaviour for background compile

* Can't remove SetAlphaTest pragmas yet

* Double new line
2024-01-26 13:58:57 -03:00
a620cbcc90 Ava UI: Mod Manager Fixes (#6179)
* Fix string format + Crash

* Better Logging

* Properly Delete Mods

Rename

* Catch when trying to access bad directory
2024-01-26 15:25:48 +01:00
cea204d48e Fs: Log when Commit fails due to PathAlreadyInUse (#6178)
* Fs: Log when Commit fails due to PathAlreadyInUse

This fixes and superseed #5418, nothing more.
(See original PR for description)

Co-Authored-By: James R T <jamestiotio@gmail.com>

* Update IFileSystem.cs

---------

Co-authored-by: James R T <jamestiotio@gmail.com>
2024-01-26 02:43:15 +01:00
35fb409e85 Ava UI: Mod Manager (#4390)
* Let’s start again

* Read folders and such

* Remove Open Mod Folder menu items

* Fix folder opening, Selecting/deselecting

* She works

* Fix GTK

* AddMod

* Delete

* Fix duplicate entries

* Fix file check

* Avalonia 11

* Style fixes

* Final style fixes

* Might be too general

* Remove unnecessary using

* Enable new mods by default

* More cleanup

* Fix saving metadata

* Dont deseralise ModMetadata several times

* Avalonia I hate you

* Confirmation dialgoues

* Allow selecting multiple folders

* Add back secondary folder

* Search both paths

* Fix formatting

* Apply suggestions from code review

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

* Rename Title to Application

* Generic locale key

* Apply suggestions from code review

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

* Locale Updates

* GDK Feedback

* Fix

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2024-01-26 02:02:28 +01:00
d7ec4308b4 Use driver name instead of vendor name in the status bar for Vulkan. (#6146)
* Replace vendor id lookup with driver name

* Create separate field for driver name, handle OpenGL

* Document changes in VulkanPhysicalDevice.cs

* Always display driver over vendor

* Replace Vulkan 1.2 requirement with VK_KHR_driver_properties

* Remove empty line

* Remove redundant unsafe block

* Apply suggestions from code review

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2024-01-26 01:07:20 +01:00
fbdd390f90 nuget: bump System.Drawing.Common from 8.0.0 to 8.0.1 (#6117)
Bumps [System.Drawing.Common](https://github.com/dotnet/winforms) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/dotnet/winforms/releases)
- [Changelog](https://github.com/dotnet/winforms/blob/main/docs/release-activity.md)
- [Commits](https://github.com/dotnet/winforms/compare/v8.0.0...v8.0.1)

---
updated-dependencies:
- dependency-name: System.Drawing.Common
  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>
2024-01-26 00:26:44 +01:00
f33fea3287 Remove Custom Theming (#6175) 2024-01-26 00:20:50 +01:00
5d3eea40be nuget: bump DynamicData from 7.14.2 to 8.3.27 (#6028)
Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 7.14.2 to 8.3.27.
- [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.14.2...8.3.27)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2024-01-26 00:17:02 +01:00
cd37c75b82 Horizon: Implement arp:r and arp:w services (#5802)
* Horizon: Implement arp:r and arp:w services

* Fix formatting

* Remove HLE arp services

* Revert "Remove HLE arp services"

This reverts commit c576fcccadb963db56b96bacabd1c1ac7abfb1ab.

* Keep LibHac impl since it's used in bcat

* Addresses gdkchan's feedback

* ArpApi in PrepoIpcServer and remove LmApi

* Fix 2

* Fixes ArpApi init

* Fix encoding

* Update PrepoService.cs

* Fix prepo
2024-01-25 23:06:53 +01:00
43705c2320 ssl: Work around missing remote hostname for authentication (#5988)
* ssl: Retrieve remote hostnames if the provided hostname is empty

 This avoids crashing with an AuthenticationException.

* ssl: Remove unused variable from RetrieveHostName
2024-01-25 20:10:51 +01:00
371e6fa24c nuget: bump Microsoft.IO.RecyclableMemoryStream from 2.3.2 to 3.0.0 (#6120)
Bumps [Microsoft.IO.RecyclableMemoryStream](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream) from 2.3.2 to 3.0.0.
- [Release notes](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream/releases)
- [Changelog](https://github.com/microsoft/Microsoft.IO.RecyclableMemoryStream/blob/master/CHANGES.md)
- [Commits](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream/compare/2.3.2...3.0.0)

---
updated-dependencies:
- dependency-name: Microsoft.IO.RecyclableMemoryStream
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-25 19:41:24 +01:00
1d9b63cc6a nuget: bump Microsoft.CodeAnalysis.CSharp from 4.7.0 to 4.8.0 (#6118)
Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) from 4.7.0 to 4.8.0.
- [Release notes](https://github.com/dotnet/roslyn/releases)
- [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md)
- [Commits](https://github.com/dotnet/roslyn/commits)

---
updated-dependencies:
- dependency-name: Microsoft.CodeAnalysis.CSharp
  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>
2024-01-25 19:35:10 +01:00
795539bc82 Vulkan: Use staging buffer for temporary constants (#6168)
* Vulkan: Use staging buffer for temporary constants

Helper shaders and post processing effects typically need some parameters to tell them what to do, which we pass via constant buffers that are created and destroyed each time.

This can vary in cost between different Vulkan drivers. It shows up on profiles on mesa and MoltenVK, so it's worth avoiding. Some games only do it once (BlitColor for present), others multiple times. It's also done for post processing filters and FSR upscaling, which creates two buffers.

For mirrors, I added the ability to reserve a range on the staging buffer for use as any type of binding. This PR allows these constant buffers to be instead temporarily allocated on the staging buffer, skipping allocation and buffer management costs entirely.

Two temporary allocations do remain:
- DrawTexture, because it doesn't have access to the command buffer scope
- Index buffer indirect conversion, because one of them is a storage buffer and thus is a little more complicated.

There's a small cost in that the uniform buffer takes up more space due to alignment requirements. At worst that's 256 bytes (on a GTX 1070) but more modern GPUs should have a better time.

Worth testing across different games and post effects to make sure they still work.

* Use temporary buffer for ConvertIndexBufferIndirect

* Simplify alignment passing for now

* Fix shader params length for CopyIncompatibleFormats

* Set data for helpershaders without overlap checks

The data is in the staging buffer, so its usage range is guarded using that.
2024-01-25 19:29:53 +01:00
dd2e851e95 OpenTK (#6143) 2024-01-25 19:25:47 +01:00
2ca70eb9a0 Implement SQSHL (immediate) CPU instruction (#6155)
* Implement SQSHL (immediate) CPU instruction

* Fix test
2024-01-24 23:50:43 +01:00
6575952432 Vulkan: Enumerate Query Pool properly (#6167)
Turns out that ElementAt for Queue<T> runs the default implementation as it doesn't implement IList, which enumerates elements of the queue up to the given index. This code was creating `count` enumerators and iterating way more queue items than it needed to at higher counts. The solution is just to use one enumerator and break out of the loop when we get the count that we need.

3.5% of backend time was being spent _just_ enumerating at the usual spot in SMO.
2024-01-24 19:33:52 -03:00
9a28ba72b1 Use unix timestamps on GetFileTimeStampRaw (#6169) 2024-01-24 19:26:59 -03:00
794 changed files with 15709 additions and 8969 deletions

View File

@ -17,8 +17,8 @@ tab_width = 4
end_of_line = lf
insert_final_newline = true
# JSON files
[*.json]
# Markdown, JSON, YAML, props and csproj files
[*.{md,json,yml,props,csproj}]
# Indentation and spacing
indent_size = 2
@ -259,12 +259,12 @@ dotnet_diagnostic.CA1861.severity = none
# Disable "Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'"
dotnet_diagnostic.CA1862.severity = none
[src/Ryujinx.HLE/HOS/Services/**.cs]
# Disable "mark members as static" rule for services
[src/Ryujinx/UI/ViewModels/**.cs]
# Disable "mark members as static" rule for ViewModels
dotnet_diagnostic.CA1822.severity = none
[src/Ryujinx.Ava/UI/ViewModels/**.cs]
# Disable "mark members as static" rule for ViewModels
[src/Ryujinx.HLE/HOS/Services/**.cs]
# Disable "mark members as static" rule for services
dotnet_diagnostic.CA1822.severity = none
[src/Ryujinx.Tests/Cpu/*.cs]

View File

@ -7,7 +7,7 @@ updates:
labels:
- "infra"
reviewers:
- marysaka
- TSRBerry
commit-message:
prefix: "ci"
@ -19,7 +19,7 @@ updates:
labels:
- "infra"
reviewers:
- marysaka
- TSRBerry
commit-message:
prefix: nuget
groups:

2
.github/labeler.yml vendored
View File

@ -20,7 +20,7 @@ gpu:
gui:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.Ui.Common/**', 'src/Ryujinx.Ui.LocaleGenerator/**', 'src/Ryujinx.Ava/**']
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Gtk3/**']
horizon:
- changed-files:

View File

@ -1,31 +1,24 @@
audio:
- marysaka
cpu:
- gdkchan
- riperiperi
- marysaka
- LDj3SNuD
gpu:
- gdkchan
- riperiperi
- marysaka
gui:
- Ack77
- emmauss
- TSRBerry
- marysaka
horizon:
- gdkchan
- Ack77
- marysaka
- TSRBerry
infra:
- marysaka
- TSRBerry
default:

View File

@ -10,28 +10,17 @@ env:
jobs:
build:
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
runs-on: ${{ matrix.os }}
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
runs-on: ${{ matrix.platform.os }}
timeout-minutes: 45
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
configuration: [Debug, Release]
include:
- os: ubuntu-latest
OS_NAME: Linux x64
DOTNET_RUNTIME_IDENTIFIER: linux-x64
RELEASE_ZIP_OS_NAME: linux_x64
- os: macOS-latest
OS_NAME: macOS x64
DOTNET_RUNTIME_IDENTIFIER: osx-x64
RELEASE_ZIP_OS_NAME: osx_x64
- os: windows-latest
OS_NAME: Windows x64
DOTNET_RUNTIME_IDENTIFIER: win-x64
RELEASE_ZIP_OS_NAME: win_x64
platform:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
- { name: osx-x64, os: macOS-latest, zip_os_name: osx_x64 }
fail-fast: false
steps:
@ -40,7 +29,7 @@ jobs:
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json"
@ -49,6 +38,16 @@ jobs:
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Change config filename
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Change config filename for macOS
run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request' && matrix.platform.os == 'macOS-latest'
- name: Build
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
@ -58,46 +57,47 @@ jobs:
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
timeout-minutes: 10
retry-codes: 139
if: matrix.platform.name != 'linux-arm64'
- name: Publish Ryujinx
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Publish Ryujinx.Headless.SDL2
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Publish Ryujinx.Ava
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
- name: Publish Ryujinx.Gtk3
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Set executable bit
run: |
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
chmod +x ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx.sh
if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest'
chmod +x ./publish_gtk/Ryujinx.Gtk3 ./publish_gtk/Ryujinx.sh
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4
with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4
with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_sdl2_headless
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Upload Ryujinx.Ava artifact
- name: Upload Ryujinx.Gtk3 artifact
uses: actions/upload-artifact@v4
with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish_ava
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_gtk
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
build_macos:
name: macOS Universal (${{ matrix.configuration }})
@ -135,19 +135,24 @@ jobs:
id: git_short_hash
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
- name: Publish macOS Ryujinx.Ava
- name: Change config filename
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request'
- name: Publish macOS Ryujinx
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Upload Ryujinx.Ava artifact
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4
with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_ava/*.tar.gz"
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish/*.tar.gz"
if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact

View File

@ -8,7 +8,7 @@ on:
- '!.github/**'
- '!*.yml'
- '!*.config'
- '!README.md'
- '!*.md'
- '.github/workflows/*.yml'
permissions:

View File

@ -51,38 +51,76 @@ jobs:
- name: Restore Nuget packages
# With .NET 8.0.100, Microsoft.NET.ILLink.Tasks isn't restored by default and only seems to appears when publishing.
# So we just publish to grab the dependencies
run: dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
run: |
dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
dotnet publish -c Release -r linux-arm64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
- name: Generate nuget_sources.json
shell: python
run: |
import hashlib
from pathlib import Path
import base64
import binascii
import json
import os
import urllib.request
sources = []
for path in Path(os.environ['NUGET_PACKAGES']).glob('**/*.nupkg.sha512'):
name = path.parent.parent.name
version = path.parent.name
filename = '{}.{}.nupkg'.format(name, version)
url = 'https://api.nuget.org/v3-flatcontainer/{}/{}/{}'.format(name, version, filename)
with path.open() as fp:
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode('ascii')
def create_source_from_external(name, version):
full_dir_path = Path(os.environ["NUGET_PACKAGES"]).joinpath(name).joinpath(version)
os.makedirs(full_dir_path, exist_ok=True)
sources.append({
'type': 'file',
'url': url,
'sha512': sha512,
'dest': os.environ['NUGET_SOURCES_DESTDIR'],
'dest-filename': filename,
})
filename = "{}.{}.nupkg".format(name, version)
url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format(
name, version, filename
)
with open('flathub/nuget_sources.json', 'w') as fp:
json.dump(sources, fp, indent=4)
print(f"Processing {url}...")
response = urllib.request.urlopen(url)
sha512 = hashlib.sha512(response.read()).hexdigest()
return {
"type": "file",
"url": url,
"sha512": sha512,
"dest": os.environ["NUGET_SOURCES_DESTDIR"],
"dest-filename": filename,
}
has_added_x64_apphost = False
for path in Path(os.environ["NUGET_PACKAGES"]).glob("**/*.nupkg.sha512"):
name = path.parent.parent.name
version = path.parent.name
filename = "{}.{}.nupkg".format(name, version)
url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format(
name, version, filename
)
with path.open() as fp:
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode("ascii")
sources.append(
{
"type": "file",
"url": url,
"sha512": sha512,
"dest": os.environ["NUGET_SOURCES_DESTDIR"],
"dest-filename": filename,
}
)
# .NET will not add current installed application host to the list, force inject it here.
if not has_added_x64_apphost and name.startswith('microsoft.netcore.app.host'):
sources.append(create_source_from_external("microsoft.netcore.app.host.linux-x64", version))
has_added_x64_apphost = True
with open("flathub/nuget_sources.json", "w") as fp:
json.dump(sources, fp, indent=4)
- name: Update flatpak metadata
id: metadata

41
.github/workflows/mako.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Mako
on:
discussion:
types: [created, edited, answered, unanswered, category_changed]
discussion_comment:
types: [created, edited]
gollum:
issue_comment:
types: [created, edited]
issues:
types: [opened, edited, reopened, pinned, milestoned, demilestoned, assigned, unassigned, labeled, unlabeled]
pull_request_target:
types: [opened, edited, reopened, synchronize, ready_for_review, assigned, unassigned]
jobs:
tasks:
name: Run Ryujinx tasks
permissions:
actions: read
contents: read
discussions: write
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
if: github.event_name == 'pull_request_target'
with:
# Ensure we pin the source origin as pull_request_target run under forks.
fetch-depth: 0
repository: Ryujinx/Ryujinx
ref: master
- name: Run Mako command
uses: Ryujinx/Ryujinx-Mako@master
with:
command: exec-ryujinx-tasks
args: --event-name "${{ github.event_name }}" --event-path "${{ github.event_path }}" -w "${{ github.workspace }}" "${{ github.repository }}" "${{ github.run_id }}"
app_id: ${{ secrets.MAKO_APP_ID }}
private_key: ${{ secrets.MAKO_PRIVATE_KEY }}
installation_id: ${{ secrets.MAKO_INSTALLATION_ID }}

View File

@ -39,24 +39,24 @@ jobs:
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
let hidden_avalonia_artifacts = `\n\n <details><summary>Experimental GUI (Avalonia)</summary>\n`;
let hidden_gtk_artifacts = `\n\n <details><summary>Old GUI (GTK3)</summary>\n`;
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
for (const art of artifacts) {
if(art.name.includes('Debug')) {
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('ava-ryujinx')) {
hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('gtk-ryujinx')) {
hidden_gtk_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('sdl2-ryujinx-headless')) {
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else {
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
}
hidden_avalonia_artifacts += `\n</details>`;
hidden_gtk_artifacts += `\n</details>`;
hidden_headless_artifacts += `\n</details>`;
hidden_debug_artifacts += `\n</details>`;
body += hidden_avalonia_artifacts;
body += hidden_gtk_artifacts;
body += hidden_headless_artifacts;
body += hidden_debug_artifacts;

View File

@ -21,27 +21,8 @@ jobs:
repository: Ryujinx/Ryujinx
ref: master
- name: Checkout Ryujinx-Mako
uses: actions/checkout@v4
with:
repository: Ryujinx/Ryujinx-Mako
ref: master
path: '.ryujinx-mako'
- name: Setup Ryujinx-Mako
uses: ./.ryujinx-mako/.github/actions/setup-mako
- name: Update labels based on changes
uses: actions/labeler@v5
with:
sync-labels: true
dot: true
- name: Assign reviewers
run: |
poetry -n -C .ryujinx-mako run ryujinx-mako update-reviewers ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml
shell: bash
env:
MAKO_APP_ID: ${{ secrets.MAKO_APP_ID }}
MAKO_PRIVATE_KEY: ${{ secrets.MAKO_PRIVATE_KEY }}
MAKO_INSTALLATION_ID: ${{ secrets.MAKO_INSTALLATION_ID }}

View File

@ -10,7 +10,7 @@ on:
- '*.yml'
- '*.json'
- '*.config'
- 'README.md'
- '*.md'
concurrency: release
@ -44,30 +44,34 @@ jobs:
sha: context.sha
})
- name: Create release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
tag: ${{ steps.version_info.outputs.build_version }}
body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
omitBodyDuringUpdate: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }}
release:
name: Release ${{ matrix.OS_NAME }}
runs-on: ${{ matrix.os }}
name: Release for ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
include:
- os: ubuntu-latest
OS_NAME: Linux x64
DOTNET_RUNTIME_IDENTIFIER: linux-x64
RELEASE_ZIP_OS_NAME: linux_x64
- os: windows-latest
OS_NAME: Windows x64
DOTNET_RUNTIME_IDENTIFIER: win-x64
RELEASE_ZIP_OS_NAME: win_x64
platform:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json"
@ -85,6 +89,7 @@ jobs:
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Create output dir
@ -92,42 +97,36 @@ jobs:
- name: Publish
run: |
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_gtk/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
- name: Packing Windows builds
if: matrix.os == 'windows-latest'
if: matrix.platform.os == 'windows-latest'
run: |
pushd publish_gtk
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
pushd publish_ava
cp publish/Ryujinx.exe publish/Ryujinx.Ava.exe
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
pushd publish_sdl2_headless
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
popd
pushd publish_ava
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
shell: bash
- name: Packing Linux builds
if: matrix.os == 'ubuntu-latest'
if: matrix.platform.os == 'ubuntu-latest'
run: |
pushd publish_gtk
chmod +x publish/Ryujinx.sh publish/Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
pushd publish_ava
cp publish/Ryujinx publish/Ryujinx.Ava
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava publish/Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
pushd publish_sdl2_headless
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
popd
pushd publish_ava
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
shell: bash
@ -186,12 +185,13 @@ jobs:
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Publish macOS Ryujinx.Ava
- name: Publish macOS Ryujinx
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release

View File

@ -3,52 +3,50 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.0.7" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.7" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.7" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.7" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.7" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.10" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.10" />
<PackageVersion Include="Avalonia" Version="11.0.10" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.10" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.10" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.10" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.16" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.16" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="7.14.2" />
<PackageVersion Include="DynamicData" Version="8.3.27" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
<PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.2.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.3.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NetCoreServer" Version="7.0.0" />
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
<PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageVersion Include="OpenTK.Core" Version="4.8.1" />
<PackageVersion Include="OpenTK.Graphics" Version="4.8.1" />
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.1" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" />
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="8.0.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.7" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
<PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup>
</Project>
</Project>

View File

@ -1,21 +1,21 @@
<h1 align="center">
<br>
<a href="https://ryujinx.org/"><img src="https://i.imgur.com/WcCj6Rt.png" alt="Ryujinx" width="150"></a>
<a href="https://ryujinx.org/"><img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx/master/distribution/misc/Logo.svg" alt="Ryujinx" width="150"></a>
<br>
<b>Ryujinx</b>
<br>
<sub><sup><b>(REE-YOU-JINX)</b></sup></sub>
<br>
</h1>
<p align="center">
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#.
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
It was written from scratch and development on the project began in September 2017. Ryujinx is available on Github under the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>. <br />
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#.
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
It was written from scratch and development on the project began in September 2017.
Ryujinx is available on Github under the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>.
<br />
</p>
<p align="center">
<a href="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml">
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
@ -34,87 +34,111 @@
<img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx-Website/master/public/assets/images/shell.png">
</p>
<h5 align="center">
</h5>
## Compatibility
As of April 2023, Ryujinx has been tested on approximately 4,050 titles; over 4,000 boot past menus and into gameplay, with roughly 3,400 of those being considered playable.
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
As of October 2023, Ryujinx has been tested on approximately 4,200 titles;
over 4,150 boot past menus and into gameplay, with roughly 3,500 of those being considered playable.
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues).
Anyone is free to submit a new game test or update an existing game test entry;
simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue.
Use the search function to see if a game has been tested already!
## Usage
To run this emulator, your PC must be equipped with at least 8GiB of RAM; failing to meet this requirement may result in a poor gameplay experience or unexpected crashes.
To run this emulator, your PC must be equipped with at least 8GiB of RAM;
failing to meet this requirement may result in a poor gameplay experience or unexpected crashes.
See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide) on how to set up the emulator.
For our Local Wireless and LAN builds, see our [Multiplayer: Local Play/Local Wireless Guide
For our Local Wireless (LDN) builds, see our [Multiplayer: Local Play/Local Wireless Guide
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide).
Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information.
## Latest build
These builds are compiled automatically for each commit on the master branch. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken.**
These builds are compiled automatically for each commit on the master branch.
While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**.
If you want to see details on updates to the emulator, you can visit our [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog).
The latest automatic build for Windows, macOS, and Linux can be found on the [Official Website](https://ryujinx.org/download).
## Documentation
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
## Building
If you wish to build the emulator yourself, follow these steps:
### Step 1
Install the X64 version of [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0).
Install the [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0).
Make sure your SDK version is higher or equal to the required version specified in [global.json](global.json).
### Step 2
Either use `git clone https://github.com/Ryujinx/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files.
### Step 3
To build Ryujinx, open a command prompt inside the project directory. You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`. Then type the following command:
`dotnet build -c Release -o build`
To build Ryujinx, open a command prompt inside the project directory.
You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`.
Then type the following command: `dotnet build -c Release -o build`
the built files will be found in the newly created build directory.
Ryujinx system files are stored in the `Ryujinx` folder. This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
Ryujinx system files are stored in the `Ryujinx` folder.
This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
## Features
- **Audio**
- **Audio**
Audio output is entirely supported, audio input (microphone) isn't supported. We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks.
Audio output is entirely supported, audio input (microphone) isn't supported.
We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks.
- **CPU**
The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support. It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code.
There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster). The fastest option (host, unchecked) is set by default.
Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads. The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game. NOTE: this feature is enabled by default in the Options menu > System tab. You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch! These improvements are permanent and do not require any extra launches going forward.
The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support.
It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code.
There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster).
The fastest option (host, unchecked) is set by default.
Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads.
The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game.
NOTE: This feature is enabled by default in the Options menu > System tab.
You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch!
These improvements are permanent and do not require any extra launches going forward.
- **GPU**
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment. These enhancements can be adjusted or toggled as desired in the GUI.
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively.
There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment.
These enhancements can be adjusted or toggled as desired in the GUI.
- **Input**
We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers. Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required.
In all scenarios, you can set up everything inside the input configuration menu.
We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers.
Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required.
In all scenarios, you can set up everything inside the input configuration menu.
- **DLC & Modifications**
Ryujinx is able to manage add-on content/downloadable content through the GUI. Mods (romfs, exefs, and runtime mods such as cheats) are also supported; the GUI contains a shortcut to open the respective mods folder for a particular game.
Ryujinx is able to manage add-on content/downloadable content through the GUI.
Mods (romfs, exefs, and runtime mods such as cheats) are also supported;
the GUI contains a shortcut to open the respective mods folder for a particular game.
- **Configuration**
The emulator has settings for enabling or disabling some logging, remapping controllers, and more. You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
## Contact
If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx). You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions).
If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx).
You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions).
## Donations
@ -134,9 +158,10 @@ All funds received through Patreon are considered a donation to support the proj
## License
This software is licensed under the terms of the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license.</a></i><br />
This software is licensed under the terms of the [MIT license](LICENSE.txt).
This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3.
See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details.
## Credits
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32228.430
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Gtk3", "src\Ryujinx.Gtk3\Ryujinx.Gtk3.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}"
EndProject
@ -69,9 +69,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ava", "src\Ryujinx.Ava\Ryujinx.Ava.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.Common", "src\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}"
EndProject
@ -79,7 +79,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.LocaleGenerator", "src\Ryujinx.Ui.LocaleGenerator\Ryujinx.Ui.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.LocaleGenerator", "src\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}"
EndProject

View File

@ -4,7 +4,7 @@ Name=Ryujinx
Type=Application
Icon=Ryujinx
Exec=Ryujinx.sh %f
Comment=Plays Nintendo Switch applications
Comment=A Nintendo Switch Emulator
GenericName=Nintendo Switch Emulator
Terminal=false
Categories=Game;Emulator;

15
distribution/linux/Ryujinx.sh Normal file → Executable file
View File

@ -1,20 +1,23 @@
#!/bin/sh
SCRIPT_DIR=$(dirname "$(realpath "$0")")
RYUJINX_BIN="Ryujinx"
if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then
RYUJINX_BIN="Ryujinx.Ava"
fi
if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
RYUJINX_BIN="Ryujinx.Headless.SDL2"
fi
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
RYUJINX_BIN="Ryujinx"
fi
if [ -z "$RYUJINX_BIN" ]; then
exit 1
fi
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
if command -v gamemoderun > /dev/null 2>&1; then
COMMAND="$COMMAND gamemoderun"
fi
$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
exec $COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"

View File

@ -14,8 +14,8 @@ mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS"
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
# Copy executables first
cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
# Copy executable and nsure executable can be executed
cp "$PUBLISH_DIRECTORY/Ryujinx" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
# Then all libraries

View File

@ -22,9 +22,9 @@ EXTRA_ARGS=$8
if [ "$VERSION" == "1.1.0" ];
then
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
else
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$VERSION-macos_universal.app.tar
RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
fi
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
@ -38,9 +38,9 @@ mkdir -p "$TEMP_DIRECTORY"
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
dotnet restore
dotnet build -c "$CONFIGURATION" src/Ryujinx.Ava
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava
dotnet build -c "$CONFIGURATION" src/Ryujinx
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
@ -108,6 +108,13 @@ tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf "$RELEASE_TAR_FILE_NAME"
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx"
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME"
# Create legacy update package for Avalonia to not left behind old testers.
if [ "$VERSION" != "1.1.0" ];
then
cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz
fi
popd
echo "Done"

View File

@ -517,7 +517,10 @@ namespace ARMeilleure.Decoders
SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create);
SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, OpCodeSimdShImm.Create);
SetA64("0x10111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_V, InstEmit.Sqrshrun_V, OpCodeSimdShImm.Create);
SetA64("010111110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Si, InstEmit.Sqshl_Si, OpCodeSimdShImm.Create);
SetA64("0>001110<<1xxxxx010011xxxxxxxxxx", InstName.Sqshl_V, InstEmit.Sqshl_V, OpCodeSimdReg.Create);
SetA64("0000111100>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create);
SetA64("010011110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create);
SetA64("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, OpCodeSimdShImm.Create);
SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create);
SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, OpCodeSimdShImm.Create);
@ -872,6 +875,7 @@ namespace ARMeilleure.Decoders
SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110110xxxx101x01x0xxxx", InstName.Vrintr, InstEmit32.Vrintr_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32);
@ -992,6 +996,7 @@ namespace ARMeilleure.Decoders
SetAsimd("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create, OpCode32SimdImm.CreateT32);
SetAsimd("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x00xxxxxxxx1101x0x0xxxx", InstName.Vpadd, InstEmit32.Vpadd_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100111x11<<00xxxx0110xxx0xxxx", InstName.Vpadal, InstEmit32.Vpadal, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);

View File

@ -1115,6 +1115,13 @@ namespace ARMeilleure.Instructions
}
}
public static void Vpadal(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
EmitVectorPairwiseTernaryLongOpI32(context, (op1, op2, op3) => context.Add(context.Add(op1, op2), op3), op.Opc != 1);
}
public static void Vpaddl(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;

View File

@ -578,6 +578,22 @@ namespace ARMeilleure.Instructions
}
}
// VRINTR (floating-point).
public static void Vrintr_S(ArmEmitterContext context)
{
if (Optimizations.UseAdvSimd)
{
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintiS);
}
else
{
EmitScalarUnaryOpF32(context, (op1) =>
{
return EmitRoundByRMode(context, op1);
});
}
}
// VRINTZ (floating-point).
public static void Vrint_Z(ArmEmitterContext context)
{

View File

@ -673,6 +673,35 @@ namespace ARMeilleure.Instructions
context.Copy(GetVecA32(op.Qd), res);
}
public static void EmitVectorPairwiseTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
int elems = op.GetBytesCount() >> op.Size;
int pairs = elems >> 1;
Operand res = GetVecA32(op.Qd);
for (int index = 0; index < pairs; index++)
{
int pairIndex = index * 2;
Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed);
Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed);
if (op.Size == 2)
{
m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1);
m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2);
}
Operand d1 = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed);
res = EmitVectorInsert(context, res, emit(m1, m2, d1), op.Id + index, op.Size + 1);
}
context.Copy(GetVecA32(op.Qd), res);
}
// Narrow
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)

View File

@ -116,7 +116,7 @@ namespace ARMeilleure.Instructions
}
else if (shift >= eSize)
{
if ((op.RegisterSize == RegisterSize.Simd64))
if (op.RegisterSize == RegisterSize.Simd64)
{
Operand res = context.VectorZeroUpper64(GetVec(op.Rd));
@ -359,6 +359,16 @@ namespace ARMeilleure.Instructions
}
}
public static void Sqshl_Si(ArmEmitterContext context)
{
EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Scalar | ShlRegFlags.Saturating);
}
public static void Sqshl_Vi(ArmEmitterContext context)
{
EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Saturating);
}
public static void Sqshrn_S(ArmEmitterContext context)
{
if (Optimizations.UseAdvSimd)
@ -1593,6 +1603,99 @@ namespace ARMeilleure.Instructions
Saturating = 1 << 3,
}
private static void EmitShlImmOp(ArmEmitterContext context, bool signedDst, ShlRegFlags flags = ShlRegFlags.None)
{
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);
bool signed = flags.HasFlag(ShlRegFlags.Signed);
bool saturating = flags.HasFlag(ShlRegFlags.Saturating);
OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp;
Operand res = context.VectorZero();
int elems = !scalar ? op.GetBytesCount() >> op.Size : 1;
for (int index = 0; index < elems; index++)
{
Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed);
Operand e = !saturating
? EmitShlImm(context, ne, GetImmShl(op), op.Size)
: EmitShlImmSatQ(context, ne, GetImmShl(op), op.Size, signed, signedDst);
res = EmitVectorInsert(context, res, e, index, op.Size);
}
context.Copy(GetVec(op.Rd), res);
}
private static Operand EmitShlImm(ArmEmitterContext context, Operand op, int shiftLsB, int size)
{
int eSize = 8 << size;
Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64);
Operand res = context.AllocateLocal(OperandType.I64);
if (shiftLsB >= eSize)
{
Operand shl = context.ShiftLeft(op, Const(shiftLsB));
context.Copy(res, shl);
}
else
{
Operand zeroL = Const(0L);
context.Copy(res, zeroL);
}
return res;
}
private static Operand EmitShlImmSatQ(ArmEmitterContext context, Operand op, int shiftLsB, int size, bool signedSrc, bool signedDst)
{
int eSize = 8 << size;
Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64);
Operand lblEnd = Label();
Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op);
if (shiftLsB >= eSize)
{
context.Copy(res, signedSrc
? EmitSignedSignSatQ(context, op, size)
: EmitUnsignedSignSatQ(context, op, size));
}
else
{
Operand shl = context.ShiftLeft(op, Const(shiftLsB));
if (eSize == 64)
{
Operand sarOrShr = signedSrc
? context.ShiftRightSI(shl, Const(shiftLsB))
: context.ShiftRightUI(shl, Const(shiftLsB));
context.Copy(res, shl);
context.BranchIf(lblEnd, sarOrShr, op, Comparison.Equal);
context.Copy(res, signedSrc
? EmitSignedSignSatQ(context, op, size)
: EmitUnsignedSignSatQ(context, op, size));
}
else
{
context.Copy(res, signedSrc
? EmitSignedSrcSatQ(context, shl, size, signedDst)
: EmitUnsignedSrcSatQ(context, shl, size, signedDst));
}
}
context.MarkLabel(lblEnd);
return res;
}
private static void EmitShlRegOp(ArmEmitterContext context, ShlRegFlags flags = ShlRegFlags.None)
{
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);

View File

@ -384,7 +384,9 @@ namespace ARMeilleure.Instructions
Sqrshrn_V,
Sqrshrun_S,
Sqrshrun_V,
Sqshl_Si,
Sqshl_V,
Sqshl_Vi,
Sqshrn_S,
Sqshrn_V,
Sqshrun_S,
@ -635,6 +637,7 @@ namespace ARMeilleure.Instructions
Vorn,
Vorr,
Vpadd,
Vpadal,
Vpaddl,
Vpmax,
Vpmin,
@ -654,6 +657,7 @@ namespace ARMeilleure.Instructions
Vrintm,
Vrintn,
Vrintp,
Vrintr,
Vrintx,
Vrshr,
Vrshrn,

View File

@ -91,54 +91,54 @@ namespace ARMeilleure.Instructions
#region "Read"
public static byte ReadByte(ulong address)
{
return GetMemoryManager().ReadTracked<byte>(address);
return GetMemoryManager().ReadGuest<byte>(address);
}
public static ushort ReadUInt16(ulong address)
{
return GetMemoryManager().ReadTracked<ushort>(address);
return GetMemoryManager().ReadGuest<ushort>(address);
}
public static uint ReadUInt32(ulong address)
{
return GetMemoryManager().ReadTracked<uint>(address);
return GetMemoryManager().ReadGuest<uint>(address);
}
public static ulong ReadUInt64(ulong address)
{
return GetMemoryManager().ReadTracked<ulong>(address);
return GetMemoryManager().ReadGuest<ulong>(address);
}
public static V128 ReadVector128(ulong address)
{
return GetMemoryManager().ReadTracked<V128>(address);
return GetMemoryManager().ReadGuest<V128>(address);
}
#endregion
#region "Write"
public static void WriteByte(ulong address, byte value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteUInt16(ulong address, ushort value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteUInt32(ulong address, uint value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteUInt64(ulong address, ulong value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteVector128(ulong address, V128 value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
#endregion

View File

@ -28,6 +28,17 @@ namespace ARMeilleure.Memory
/// <returns>The data</returns>
T ReadTracked<T>(ulong va) where T : unmanaged;
/// <summary>
/// Reads data from CPU mapped memory, from guest code. (with read tracking)
/// </summary>
/// <typeparam name="T">Type of the data being read</typeparam>
/// <param name="va">Virtual address of the data in memory</param>
/// <returns>The data</returns>
T ReadGuest<T>(ulong va) where T : unmanaged
{
return ReadTracked<T>(va);
}
/// <summary>
/// Writes data to CPU mapped memory.
/// </summary>
@ -36,6 +47,17 @@ namespace ARMeilleure.Memory
/// <param name="value">Data to be written</param>
void Write<T>(ulong va, T value) where T : unmanaged;
/// <summary>
/// Writes data to CPU mapped memory, from guest code.
/// </summary>
/// <typeparam name="T">Type of the data being written</typeparam>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="value">Data to be written</param>
void WriteGuest<T>(ulong va, T value) where T : unmanaged
{
Write(va, value);
}
/// <summary>
/// Gets a read-only span of data from CPU mapped memory.
/// </summary>

View File

@ -20,6 +20,25 @@ namespace Ryujinx.Audio.Backends.OpenAL
private bool _stillRunning;
private readonly Thread _updaterThread;
private float _volume;
public float Volume
{
get
{
return _volume;
}
set
{
_volume = value;
foreach (OpenALHardwareDeviceSession session in _sessions.Keys)
{
session.UpdateMasterVolume(value);
}
}
}
public OpenALHardwareDeviceDriver()
{
_device = ALC.OpenDevice("");
@ -34,6 +53,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
Name = "HardwareDeviceDriver.OpenAL",
};
_volume = 1f;
_updaterThread.Start();
}
@ -52,7 +73,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
}
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -73,7 +94,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
throw new ArgumentException($"{channelCount}");
}
OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);

View File

@ -16,10 +16,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
private bool _isActive;
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
private ulong _playedSampleCount;
private float _volume;
private readonly object _lock = new();
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_queuedBuffers = new Queue<OpenALAudioBuffer>();
@ -27,7 +28,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
_targetFormat = GetALFormat();
_isActive = false;
_playedSampleCount = 0;
SetVolume(requestedVolume);
SetVolume(1f);
}
private ALFormat GetALFormat()
@ -85,17 +86,22 @@ namespace Ryujinx.Audio.Backends.OpenAL
public override void SetVolume(float volume)
{
lock (_lock)
{
AL.Source(_sourceId, ALSourcef.Gain, volume);
}
_volume = volume;
UpdateMasterVolume(_driver.Volume);
}
public override float GetVolume()
{
AL.GetSource(_sourceId, ALSourcef.Gain, out float volume);
return _volume;
}
return volume;
public void UpdateMasterVolume(float newVolume)
{
lock (_lock)
{
AL.Source(_sourceId, ALSourcef.Gain, newVolume * _volume);
}
}
public override void Start()

View File

@ -20,6 +20,8 @@ namespace Ryujinx.Audio.Backends.SDL2
private readonly bool _supportSurroundConfiguration;
public float Volume { get; set; }
// TODO: Add this to SDL2-CS
// NOTE: We use a DllImport here because of marshaling issue for spec.
#pragma warning disable SYSLIB1054
@ -48,6 +50,8 @@ namespace Ryujinx.Audio.Backends.SDL2
{
_supportSurroundConfiguration = spec.channels >= 6;
}
Volume = 1f;
}
public static bool IsSupported => IsSupportedInternal();
@ -74,7 +78,7 @@ namespace Ryujinx.Audio.Backends.SDL2
return _pauseEvent;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -91,7 +95,7 @@ namespace Ryujinx.Audio.Backends.SDL2
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
}
SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);

View File

@ -26,7 +26,7 @@ namespace Ryujinx.Audio.Backends.SDL2
private float _volume;
private readonly ushort _nativeSampleFormat;
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
@ -37,7 +37,7 @@ namespace Ryujinx.Audio.Backends.SDL2
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
_sampleCount = uint.MaxValue;
_started = false;
_volume = requestedVolume;
_volume = 1f;
}
private void EnsureAudioStreamSetup(AudioBuffer buffer)
@ -99,7 +99,7 @@ namespace Ryujinx.Audio.Backends.SDL2
streamSpan.Clear();
// Apply volume to written data
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME));
}
ulong sampleCount = GetSampleCount(samples.Length);

View File

@ -11,15 +11,15 @@
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'">
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dylib</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.so</TargetPath>
</ContentWithTargetPath>

View File

@ -19,6 +19,25 @@ namespace Ryujinx.Audio.Backends.SoundIo
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
private int _disposeState;
private float _volume = 1f;
public float Volume
{
get
{
return _volume;
}
set
{
_volume = value;
foreach (SoundIoHardwareDeviceSession session in _sessions.Keys)
{
session.UpdateMasterVolume(value);
}
}
}
public SoundIoHardwareDeviceDriver()
{
_audioContext = SoundIoContext.Create();
@ -122,7 +141,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
return _pauseEvent;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -134,14 +153,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
sampleRate = Constants.TargetSampleRate;
}
volume = Math.Clamp(volume, 0, 1);
if (direction != Direction.Output)
{
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
}
SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);

View File

@ -18,16 +18,18 @@ namespace Ryujinx.Audio.Backends.SoundIo
private readonly DynamicRingBuffer _ringBuffer;
private ulong _playedSampleCount;
private readonly ManualResetEvent _updateRequiredEvent;
private float _volume;
private int _disposeState;
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
_ringBuffer = new DynamicRingBuffer();
_volume = 1f;
SetupOutputStream(requestedVolume);
SetupOutputStream(driver.Volume);
}
private void SetupOutputStream(float requestedVolume)
@ -47,7 +49,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
public override float GetVolume()
{
return _outputStream.Volume;
return _volume;
}
public override void PrepareToClose() { }
@ -63,7 +65,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
public override void SetVolume(float volume)
{
_outputStream.SetVolume(volume);
_volume = volume;
_outputStream.SetVolume(_driver.Volume * volume);
}
public void UpdateMasterVolume(float newVolume)
{
_outputStream.SetVolume(newVolume * _volume);
}
public override void Start()

View File

@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
public static bool IsSupported => true;
public float Volume
{
get => _realDriver.Volume;
set => _realDriver.Volume = value;
}
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
{
_realDriver = realDevice;
@ -90,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
throw new ArgumentException("No valid sample format configuration found!");
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -102,8 +108,6 @@ namespace Ryujinx.Audio.Backends.CompatLayer
sampleRate = Constants.TargetSampleRate;
}
volume = Math.Clamp(volume, 0, 1);
if (!_realDriver.SupportsDirection(direction))
{
if (direction == Direction.Input)
@ -119,7 +123,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount, volume);
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount);
if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat)
{

View File

@ -14,13 +14,17 @@ namespace Ryujinx.Audio.Backends.Dummy
public static bool IsSupported => true;
public float Volume { get; set; }
public DummyHardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true);
Volume = 1f;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (sampleRate == 0)
{
@ -34,7 +38,7 @@ namespace Ryujinx.Audio.Backends.Dummy
if (direction == Direction.Output)
{
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount);
}
return new DummyHardwareDeviceSessionInput(this, memoryManager);

View File

@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Backends.Dummy
private ulong _playedSampleCount;
public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_volume = requestedVolume;
_volume = 1f;
_manager = manager;
}

View File

@ -166,7 +166,6 @@ namespace Ryujinx.Audio.Input
/// </summary>
/// <param name="filtered">If true, filter disconnected devices</param>
/// <returns>The list of all audio inputs name</returns>
#pragma warning disable CA1822 // Mark member as static
public string[] ListAudioIns(bool filtered)
{
if (filtered)
@ -176,7 +175,6 @@ namespace Ryujinx.Audio.Input
return new[] { Constants.DefaultDeviceInputName };
}
#pragma warning restore CA1822
/// <summary>
/// Open a new <see cref="AudioInputSystem"/>.
@ -188,8 +186,6 @@ namespace Ryujinx.Audio.Input
/// <param name="inputDeviceName">The input device name wanted by the user</param>
/// <param name="sampleFormat">The sample format to use</param>
/// <param name="parameter">The user configuration</param>
/// <param name="appletResourceUserId">The applet resource user id of the application</param>
/// <param name="processHandle">The process handle of the application</param>
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
public ResultCode OpenAudioIn(out string outputDeviceName,
out AudioOutputConfiguration outputConfiguration,
@ -197,9 +193,7 @@ namespace Ryujinx.Audio.Input
IVirtualMemoryManager memoryManager,
string inputDeviceName,
SampleFormat sampleFormat,
ref AudioInputConfiguration parameter,
ulong appletResourceUserId,
uint processHandle)
ref AudioInputConfiguration parameter)
{
int sessionId = AcquireSessionId();

View File

@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Integration
private readonly byte[] _buffer;
public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate, float volume)
public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate)
{
_session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount, volume);
_session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount);
_channelCount = channelCount;
_sampleRate = sampleRate;
_currentBufferTag = 0;

View File

@ -16,7 +16,9 @@ namespace Ryujinx.Audio.Integration
Output,
}
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume = 1f);
float Volume { get; set; }
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount);
ManualResetEvent GetUpdateRequiredEvent();
ManualResetEvent GetPauseEvent();

View File

@ -165,12 +165,10 @@ namespace Ryujinx.Audio.Output
/// Get the list of all audio outputs name.
/// </summary>
/// <returns>The list of all audio outputs name</returns>
#pragma warning disable CA1822 // Mark member as static
public string[] ListAudioOuts()
{
return new[] { Constants.DefaultDeviceOutputName };
}
#pragma warning restore CA1822
/// <summary>
/// Open a new <see cref="AudioOutputSystem"/>.
@ -182,9 +180,6 @@ namespace Ryujinx.Audio.Output
/// <param name="inputDeviceName">The input device name wanted by the user</param>
/// <param name="sampleFormat">The sample format to use</param>
/// <param name="parameter">The user configuration</param>
/// <param name="appletResourceUserId">The applet resource user id of the application</param>
/// <param name="processHandle">The process handle of the application</param>
/// <param name="volume">The volume level to request</param>
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
public ResultCode OpenAudioOut(out string outputDeviceName,
out AudioOutputConfiguration outputConfiguration,
@ -192,16 +187,13 @@ namespace Ryujinx.Audio.Output
IVirtualMemoryManager memoryManager,
string inputDeviceName,
SampleFormat sampleFormat,
ref AudioInputConfiguration parameter,
ulong appletResourceUserId,
uint processHandle,
float volume)
ref AudioInputConfiguration parameter)
{
int sessionId = AcquireSessionId();
_sessionsBufferEvents[sessionId].Clear();
IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume);
IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount);
AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]);
@ -234,41 +226,6 @@ namespace Ryujinx.Audio.Output
return result;
}
/// <summary>
/// Sets the volume for all output devices.
/// </summary>
/// <param name="volume">The volume to set.</param>
public void SetVolume(float volume)
{
if (_sessions != null)
{
foreach (AudioOutputSystem session in _sessions)
{
session?.SetVolume(volume);
}
}
}
/// <summary>
/// Gets the volume for all output devices.
/// </summary>
/// <returns>A float indicating the volume level.</returns>
public float GetVolume()
{
if (_sessions != null)
{
foreach (AudioOutputSystem session in _sessions)
{
if (session != null)
{
return session.GetVolume();
}
}
}
return 0.0f;
}
public void Dispose()
{
GC.SuppressFinalize(this);

View File

@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
_event = new ManualResetEvent(false);
}
#pragma warning disable IDE0051 // Remove unused private member
private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
{
// Get the real device driver (In case the compat layer is on top of it).
@ -59,9 +58,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
// NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible.
return 2;
}
#pragma warning restore IDE0051
public void Start(IHardwareDeviceDriver deviceDriver, float volume)
public void Start(IHardwareDeviceDriver deviceDriver)
{
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
@ -70,7 +68,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
for (int i = 0; i < OutputDevices.Length; i++)
{
// TODO: Don't hardcode sample rate.
OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume);
OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate);
}
_mailbox = new Mailbox<MailboxMessage>();
@ -231,33 +229,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
_mailbox.SendResponse(MailboxMessage.Stop);
}
public float GetVolume()
{
if (OutputDevices != null)
{
foreach (IHardwareDevice outputDevice in OutputDevices)
{
if (outputDevice != null)
{
return outputDevice.GetVolume();
}
}
}
return 0f;
}
public void SetVolume(float volume)
{
if (OutputDevices != null)
{
foreach (IHardwareDevice outputDevice in OutputDevices)
{
outputDevice?.SetVolume(volume);
}
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
@ -269,6 +240,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
if (disposing)
{
_event.Dispose();
_mailbox?.Dispose();
}
}
}

View File

@ -177,12 +177,12 @@ namespace Ryujinx.Audio.Renderer.Server
/// <summary>
/// Start the <see cref="AudioProcessor"/> and worker thread.
/// </summary>
private void StartLocked(float volume)
private void StartLocked()
{
_isRunning = true;
// TODO: virtual device mapping (IAudioDevice)
Processor.Start(_deviceDriver, volume);
Processor.Start(_deviceDriver);
_workerThread = new Thread(SendCommands)
{
@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// Register a new <see cref="AudioRenderSystem"/>.
/// </summary>
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
private void Register(AudioRenderSystem renderer, float volume)
private void Register(AudioRenderSystem renderer)
{
lock (_sessionLock)
{
@ -265,7 +265,7 @@ namespace Ryujinx.Audio.Renderer.Server
{
if (!_isRunning)
{
StartLocked(volume);
StartLocked();
}
}
}
@ -312,8 +312,7 @@ namespace Ryujinx.Audio.Renderer.Server
ulong appletResourceUserId,
ulong workBufferAddress,
ulong workBufferSize,
uint processHandle,
float volume)
uint processHandle)
{
int sessionId = AcquireSessionId();
@ -338,7 +337,7 @@ namespace Ryujinx.Audio.Renderer.Server
{
renderer = audioRenderer;
Register(renderer, volume);
Register(renderer);
}
else
{
@ -350,21 +349,6 @@ namespace Ryujinx.Audio.Renderer.Server
return result;
}
public float GetVolume()
{
if (Processor != null)
{
return Processor.GetVolume();
}
return 0f;
}
public void SetVolume(float volume)
{
Processor?.SetVolume(volume);
}
public void Dispose()
{
GC.SuppressFinalize(this);

View File

@ -1,777 +0,0 @@
using Avalonia.Controls;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip;
using Ryujinx.Ava;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.Ui.Common.Models.Github;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Modules
{
internal static class Updater
{
private const string GitHubApiUrl = "https://api.github.com";
private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
private const int ConnectionCount = 4;
private static string _buildVer;
private static string _platformExt;
private static string _buildUrl;
private static long _buildSize;
private static bool _updateSuccessful;
private static bool _running;
private static readonly string[] _windowsDependencyDirs = Array.Empty<string>();
public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate)
{
if (_running)
{
return;
}
_running = true;
// Detect current platform
if (OperatingSystem.IsMacOS())
{
_platformExt = "macos_universal.app.tar.gz";
}
else if (OperatingSystem.IsWindows())
{
_platformExt = "win_x64.zip";
}
else if (OperatingSystem.IsLinux())
{
_platformExt = "linux_x64.tar.gz";
}
Version newVersion;
Version currentVersion;
try
{
currentVersion = Version.Parse(Program.Version);
}
catch
{
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
_running = false;
return;
}
// Get latest version number from GitHub API
try
{
using HttpClient jsonClient = ConstructHttpClient();
string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl);
var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse);
_buildVer = fetched.Name;
foreach (var asset in fetched.Assets)
{
if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt))
{
_buildUrl = asset.BrowserDownloadUrl;
if (asset.State != "uploaded")
{
if (showVersionUpToDate)
{
await ContentDialogHelper.CreateUpdaterInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
}
_running = false;
return;
}
break;
}
}
// If build not done, assume no new update are available.
if (_buildUrl is null)
{
if (showVersionUpToDate)
{
await ContentDialogHelper.CreateUpdaterInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
}
_running = false;
return;
}
}
catch (Exception exception)
{
Logger.Error?.Print(LogClass.Application, exception.Message);
await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
_running = false;
return;
}
try
{
newVersion = Version.Parse(_buildVer);
}
catch
{
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
_running = false;
return;
}
if (newVersion <= currentVersion)
{
if (showVersionUpToDate)
{
await ContentDialogHelper.CreateUpdaterInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
}
_running = false;
return;
}
// Fetch build size information to learn chunk sizes.
using HttpClient buildSizeClient = ConstructHttpClient();
try
{
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead);
_buildSize = message.Content.Headers.ContentRange.Length.Value;
}
catch (Exception ex)
{
Logger.Warning?.Print(LogClass.Application, ex.Message);
Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
_buildSize = -1;
}
await Dispatcher.UIThread.InvokeAsync(async () =>
{
// Show a message asking the user if they want to update
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(
LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
$"{Program.Version} -> {newVersion}");
if (shouldUpdate)
{
await UpdateRyujinx(mainWindow, _buildUrl);
}
else
{
_running = false;
}
});
}
private static HttpClient ConstructHttpClient()
{
HttpClient result = new();
// Required by GitHub to interact with APIs.
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
return result;
}
private static async Task UpdateRyujinx(Window parent, string downloadUrl)
{
_updateSuccessful = false;
// Empty update dir, although it shouldn't ever have anything inside it
if (Directory.Exists(_updateDir))
{
Directory.Delete(_updateDir, true);
}
Directory.CreateDirectory(_updateDir);
string updateFile = Path.Combine(_updateDir, "update.bin");
TaskDialog taskDialog = new()
{
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
ShowProgressBar = true,
XamlRoot = parent,
};
taskDialog.Opened += (s, e) =>
{
if (_buildSize >= 0)
{
DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile);
}
else
{
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
}
};
await taskDialog.ShowAsync(true);
if (_updateSuccessful)
{
bool shouldRestart = true;
if (!OperatingSystem.IsMacOS())
{
shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
}
if (shouldRestart)
{
List<string> arguments = CommandLineState.Arguments.ToList();
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
// On macOS we perform the update at relaunch.
if (OperatingSystem.IsMacOS())
{
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app");
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
string currentPid = Environment.ProcessId.ToString();
arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
Process.Start("/bin/bash", arguments);
}
else
{
// Find the process name.
string ryuName = Path.GetFileName(Environment.ProcessPath);
// Some operating systems can see the renamed executable, so strip off the .ryuold if found.
if (ryuName.EndsWith(".ryuold"))
{
ryuName = ryuName[..^7];
}
// Fallback if the executable could not be found.
if (!Path.Exists(Path.Combine(executableDirectory, ryuName)))
{
ryuName = OperatingSystem.IsWindows() ? "Ryujinx.Ava.exe" : "Ryujinx.Ava";
}
ProcessStartInfo processStart = new(ryuName)
{
UseShellExecute = true,
WorkingDirectory = executableDirectory,
};
foreach (string argument in CommandLineState.Arguments)
{
processStart.ArgumentList.Add(argument);
}
Process.Start(processStart);
}
Environment.Exit(0);
}
}
}
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
{
// Multi-Threaded Updater
long chunkSize = _buildSize / ConnectionCount;
long remainderChunk = _buildSize % ConnectionCount;
int completedRequests = 0;
int totalProgressPercentage = 0;
int[] progressPercentage = new int[ConnectionCount];
List<byte[]> list = new(ConnectionCount);
List<WebClient> webClients = new(ConnectionCount);
for (int i = 0; i < ConnectionCount; i++)
{
list.Add(Array.Empty<byte>());
}
for (int i = 0; i < ConnectionCount; i++)
{
#pragma warning disable SYSLIB0014
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
using WebClient client = new();
#pragma warning restore SYSLIB0014
webClients.Add(client);
if (i == ConnectionCount - 1)
{
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
}
else
{
client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}");
}
client.DownloadProgressChanged += (_, args) =>
{
int index = (int)args.UserState;
Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]);
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
};
client.DownloadDataCompleted += (_, args) =>
{
int index = (int)args.UserState;
if (args.Cancelled)
{
webClients[index].Dispose();
taskDialog.Hide();
return;
}
list[index] = args.Result;
Interlocked.Increment(ref completedRequests);
if (Equals(completedRequests, ConnectionCount))
{
byte[] mergedFileBytes = new byte[_buildSize];
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
{
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
destinationOffset += list[connectionIndex].Length;
}
File.WriteAllBytes(updateFile, mergedFileBytes);
// On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution.
if (OperatingSystem.IsMacOS())
{
using Process xattrProcess = Process.Start("xattr", new List<string> { "-d", "com.apple.quarantine", updateFile });
xattrProcess.WaitForExit();
}
try
{
InstallUpdate(taskDialog, updateFile);
}
catch (Exception e)
{
Logger.Warning?.Print(LogClass.Application, e.Message);
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
}
}
};
try
{
client.DownloadDataAsync(new Uri(downloadUrl), i);
}
catch (WebException ex)
{
Logger.Warning?.Print(LogClass.Application, ex.Message);
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
foreach (WebClient webClient in webClients)
{
webClient.CancelAsync();
}
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
return;
}
}
}
private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile)
{
using HttpClient client = new();
// We do not want to timeout while downloading
client.Timeout = TimeSpan.FromDays(1);
using HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result;
using Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result;
using Stream updateFileStream = File.Open(updateFile, FileMode.Create);
long totalBytes = response.Content.Headers.ContentLength.Value;
long byteWritten = 0;
byte[] buffer = new byte[32 * 1024];
while (true)
{
int readSize = remoteFileStream.Read(buffer);
if (readSize == 0)
{
break;
}
byteWritten += readSize;
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal);
updateFileStream.Write(buffer, 0, readSize);
}
InstallUpdate(taskDialog, updateFile);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double GetPercentage(double value, double max)
{
return max == 0 ? 0 : value / max * 100;
}
private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
{
Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile))
{
Name = "Updater.SingleThreadWorker",
};
worker.Start();
}
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
{
using Stream inStream = File.OpenRead(archivePath);
using GZipInputStream gzipStream = new(inStream);
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
TarEntry tarEntry;
while ((tarEntry = tarStream.GetNextEntry()) is not null)
{
if (tarEntry.IsDirectory)
{
continue;
}
string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using FileStream outStream = File.OpenWrite(outPath);
tarStream.CopyEntryContents(outStream);
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
Dispatcher.UIThread.Post(() =>
{
if (tarEntry is null)
{
return;
}
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
});
}
}
private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
{
using Stream inStream = File.OpenRead(archivePath);
using ZipFile zipFile = new(inStream);
double count = 0;
foreach (ZipEntry zipEntry in zipFile)
{
count++;
if (zipEntry.IsDirectory)
{
continue;
}
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using Stream zipStream = zipFile.GetInputStream(zipEntry);
using FileStream outStream = File.OpenWrite(outPath);
zipStream.CopyTo(outStream);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
Dispatcher.UIThread.Post(() =>
{
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
});
}
}
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
{
// Extract Update
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
}
else if (OperatingSystem.IsWindows())
{
ExtractZipFile(taskDialog, updateFile, _updateDir);
}
else
{
throw new NotSupportedException();
}
// Delete downloaded zip
File.Delete(updateFile);
List<string> allFiles = EnumerateFilesToDelete().ToList();
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
// NOTE: On macOS, replacement is delayed to the restart phase.
if (!OperatingSystem.IsMacOS())
{
// Replace old files
double count = 0;
foreach (string file in allFiles)
{
count++;
try
{
File.Move(file, file + ".ryuold");
Dispatcher.UIThread.InvokeAsync(() =>
{
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
});
}
catch
{
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
}
}
Dispatcher.UIThread.InvokeAsync(() =>
{
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
});
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog);
Directory.Delete(_updateDir, true);
}
_updateSuccessful = true;
taskDialog.Hide();
}
public static bool CanUpdate(bool showWarnings)
{
#if !DISABLE_UPDATER
if (RuntimeInformation.OSArchitecture != Architecture.X64 && !OperatingSystem.IsMacOS())
{
if (showWarnings)
{
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage])
);
}
return false;
}
if (!NetworkInterface.GetIsNetworkAvailable())
{
if (showWarnings)
{
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage])
);
}
return false;
}
if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid())
{
if (showWarnings)
{
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
);
}
return false;
}
return true;
#else
if (showWarnings)
{
if (ReleaseInformation.IsFlatHubBuild())
{
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage])
);
}
else
{
Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
);
}
}
return false;
#endif
}
// NOTE: This method should always reflect the latest build layout.
private static IEnumerable<string> EnumerateFilesToDelete()
{
var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir.
// Determine and exclude user files only when the updater is running, not when cleaning old files
if (_running && !OperatingSystem.IsMacOS())
{
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var newFiles = Directory.EnumerateFiles(_updatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(_homeDir, filename));
// Remove user files from the paths in files.
files = files.Except(userFiles);
}
if (OperatingSystem.IsWindows())
{
foreach (string dir in _windowsDependencyDirs)
{
string dirPath = Path.Combine(_homeDir, dir);
if (Directory.Exists(dirPath))
{
files = files.Concat(Directory.EnumerateFiles(dirPath, "*", SearchOption.AllDirectories));
}
}
}
return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System));
}
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog)
{
int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length;
foreach (string directory in Directory.GetDirectories(root))
{
string dirName = Path.GetFileName(directory);
if (!Directory.Exists(Path.Combine(dest, dirName)))
{
Directory.CreateDirectory(Path.Combine(dest, dirName));
}
MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog);
}
double count = 0;
foreach (string file in Directory.GetFiles(root))
{
count++;
File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
Dispatcher.UIThread.InvokeAsync(() =>
{
taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal);
});
}
}
public static void CleanupUpdate()
{
foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories))
{
File.Delete(file);
}
}
}
}

View File

@ -1,236 +0,0 @@
using Avalonia;
using Avalonia.Threading;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using Ryujinx.Ui.Common.SystemInfo;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Ryujinx.Ava
{
internal partial class Program
{
public static double WindowScaleFactor { get; set; }
public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
[LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
private const uint MbIconwarning = 0x30;
public static void Main(string[] args)
{
Version = ReleaseInformation.GetVersion();
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{
_ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
}
PreviewerDetached = true;
Initialize(args);
LoggerAdapter.Register();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.With(new X11PlatformOptions
{
EnableMultiTouch = true,
EnableIme = true,
RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software },
})
.With(new Win32PlatformOptions
{
WinUICompositionBackdropCornerRadius = 8.0f,
RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software },
})
.UseSkia();
}
private static void Initialize(string[] args)
{
// Parse arguments
CommandLineState.ParseArguments(args);
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
Console.Title = $"Ryujinx Console {Version}";
// Hook unhandled exception and process exit events.
AppDomain.CurrentDomain.UnhandledException += (sender, e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.ProcessExit += (sender, e) => Exit();
// Setup base data directory.
AppDataManager.Initialize(CommandLineState.BaseDirPathArg);
// Initialize the configuration.
ConfigurationState.Initialize();
// Initialize the logger system.
LoggerModule.Initialize();
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();
// Initialize SDL2 driver
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
ReloadConfig();
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
// Logging system information.
PrintSystemInfo();
// Enable OGL multithreading on the driver, when available.
DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
// Check if keys exists.
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
{
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
{
MainWindow.ShowKeyErrorOnLoad = true;
}
}
if (CommandLineState.LaunchPathArg != null)
{
MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
}
}
public static void ReloadConfig()
{
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
// Now load the configuration as the other subsystems are now registered
if (File.Exists(localConfigurationPath))
{
ConfigurationPath = localConfigurationPath;
}
else if (File.Exists(appDataConfigurationPath))
{
ConfigurationPath = appDataConfigurationPath;
}
if (ConfigurationPath == null)
{
// No configuration, we load the default values and save it to disk
ConfigurationPath = appDataConfigurationPath;
ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
}
else
{
if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat))
{
ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath);
}
else
{
ConfigurationState.Instance.LoadDefault();
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}");
}
}
// Check if graphics backend was overridden
if (CommandLineState.OverrideGraphicsBackend != null)
{
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
}
else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
}
}
// Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
}
// Check if HideCursor was overridden.
if (CommandLineState.OverrideHideCursor is not null)
{
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
{
"never" => HideCursorMode.Never,
"onidle" => HideCursorMode.OnIdle,
"always" => HideCursorMode.Always,
_ => ConfigurationState.Instance.HideCursor.Value,
};
}
}
private static void PrintSystemInfo()
{
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
SystemInfo.Gather().Print();
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}");
}
else
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}");
}
}
private static void ProcessUnhandledException(Exception ex, bool isTerminating)
{
string message = $"Unhandled exception caught: {ex}";
Logger.Error?.PrintMsg(LogClass.Application, message);
if (Logger.Error == null)
{
Logger.Notice.PrintMsg(LogClass.Application, message);
}
if (isTerminating)
{
Exit();
}
}
public static void Exit()
{
DiscordIntegrationModule.Exit();
Logger.Shutdown();
}
}
}

View File

@ -1,168 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
<RootNamespace>Ryujinx.Ava</RootNamespace>
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
<TieredPGO>true</TieredPGO>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<Exec Command="codesign --entitlements '$(ProjectDir)..\..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
</Target>
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
<PublishSingleFile>true</PublishSingleFile>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
<!--
FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json.
See:
https://github.com/amwx/FluentAvalonia/issues/481
https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-8/
-->
<PropertyGroup>
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Desktop" />
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
<PackageReference Include="Avalonia.Svg" />
<PackageReference Include="Avalonia.Svg.Skia" />
<PackageReference Include="jp2masa.Avalonia.Flexbox" />
<PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
<PackageReference Include="Silk.NET.Vulkan" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="SixLabors.ImageSharp" />
<!--NOTE: DO NOT REMOVE, THIS IS REQUIRED AS A RESULT OF A TRIMMING ISSUE IN AVALONIA -->
<PackageReference Include="System.Drawing.Common" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Ui.LocaleGenerator\Ryujinx.Ui.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>alsoft.ini</TargetPath>
</Content>
<Content Include="..\..\distribution\legal\THIRDPARTY.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>THIRDPARTY.md</TargetPath>
</Content>
<Content Include="..\..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>LICENSE.txt</TargetPath>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
<Content Include="..\..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\..\distribution\linux\mime\Ryujinx.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>mime\Ryujinx.xml</TargetPath>
</Content>
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Ui\**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Fonts\SegoeFluentIcons.ttf" />
<AvaloniaResource Include="Assets\Styles\Themes.xaml">
<Generator>MSBuild:Compile</Generator>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Styles\Styles.xaml" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\Locales\el_GR.json" />
<None Remove="Assets\Locales\en_US.json" />
<None Remove="Assets\Locales\es_ES.json" />
<None Remove="Assets\Locales\fr_FR.json" />
<None Remove="Assets\Locales\he_IL.json" />
<None Remove="Assets\Locales\de_DE.json" />
<None Remove="Assets\Locales\it_IT.json" />
<None Remove="Assets\Locales\ja_JP.json" />
<None Remove="Assets\Locales\ko_KR.json" />
<None Remove="Assets\Locales\pl_PL.json" />
<None Remove="Assets\Locales\pt_BR.json" />
<None Remove="Assets\Locales\ru_RU.json" />
<None Remove="Assets\Locales\tr_TR.json" />
<None Remove="Assets\Locales\uk_UA.json" />
<None Remove="Assets\Locales\zh_CN.json" />
<None Remove="Assets\Locales\zh_TW.json" />
<None Remove="Assets\Styles\Styles.xaml" />
<None Remove="Assets\Styles\Themes.xaml" />
<None Remove="Assets\Icons\Controller_JoyConLeft.svg" />
<None Remove="Assets\Icons\Controller_JoyConPair.svg" />
<None Remove="Assets\Icons\Controller_JoyConRight.svg" />
<None Remove="Assets\Icons\Controller_ProCon.svg" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\Locales\el_GR.json" />
<EmbeddedResource Include="Assets\Locales\en_US.json" />
<EmbeddedResource Include="Assets\Locales\es_ES.json" />
<EmbeddedResource Include="Assets\Locales\fr_FR.json" />
<EmbeddedResource Include="Assets\Locales\he_IL.json" />
<EmbeddedResource Include="Assets\Locales\de_DE.json" />
<EmbeddedResource Include="Assets\Locales\it_IT.json" />
<EmbeddedResource Include="Assets\Locales\ja_JP.json" />
<EmbeddedResource Include="Assets\Locales\ko_KR.json" />
<EmbeddedResource Include="Assets\Locales\pl_PL.json" />
<EmbeddedResource Include="Assets\Locales\pt_BR.json" />
<EmbeddedResource Include="Assets\Locales\ru_RU.json" />
<EmbeddedResource Include="Assets\Locales\tr_TR.json" />
<EmbeddedResource Include="Assets\Locales\uk_UA.json" />
<EmbeddedResource Include="Assets\Locales\zh_CN.json" />
<EmbeddedResource Include="Assets\Locales\zh_TW.json" />
<EmbeddedResource Include="Assets\Styles\Styles.xaml" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_ProCon.svg" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Assets\Locales\en_US.json" />
</ItemGroup>
</Project>

View File

@ -1,13 +1,15 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using System;
using System.IO;
using System.Runtime.Versioning;
namespace Ryujinx.Common.Configuration
{
public static class AppDataManager
{
public const string DefaultBaseDir = "Ryujinx";
public const string DefaultPortableDir = "portable";
private const string DefaultBaseDir = "Ryujinx";
private const string DefaultPortableDir = "portable";
// The following 3 are always part of Base Directory
private const string GamesDir = "games";
@ -29,6 +31,8 @@ namespace Ryujinx.Common.Configuration
public static string KeysDirPath { get; private set; }
public static string KeysDirPathUser { get; }
public static string LogsDirPath { get; private set; }
public const string DefaultNandDir = "bis";
public const string DefaultSdcardDir = "sdcard";
private const string DefaultModsDir = "mods";
@ -45,15 +49,7 @@ namespace Ryujinx.Common.Configuration
public static void Initialize(string baseDirPath)
{
string appDataPath;
if (OperatingSystem.IsMacOS())
{
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support");
}
else
{
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
}
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
if (appDataPath.Length == 0)
{
@ -100,65 +96,227 @@ namespace Ryujinx.Common.Configuration
BaseDirPath = Path.GetFullPath(BaseDirPath); // convert relative paths
// NOTE: Moves the Ryujinx folder in `~/.config` to `~/Library/Application Support` if one is found
// and a Ryujinx folder does not already exist in Application Support.
// Also creates a symlink from `~/.config/Ryujinx` to `~/Library/Application Support/Ryujinx` to preserve backwards compatibility.
// This should be removed in the future.
if (OperatingSystem.IsMacOS() && Mode == LaunchMode.UserProfile)
if (IsPathSymlink(BaseDirPath))
{
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
{
CopyDirectory(oldConfigPath, BaseDirPath);
Directory.Delete(oldConfigPath, true);
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
}
Logger.Warning?.Print(LogClass.Application, $"Application data directory is a symlink. This may be unintended.");
}
SetupBasePaths();
}
public static string GetOrCreateLogsDir()
{
if (Directory.Exists(LogsDirPath))
{
return LogsDirPath;
}
Logger.Notice.Print(LogClass.Application, "Logging directory not found; attempting to create new logging directory.");
LogsDirPath = SetUpLogsDir();
return LogsDirPath;
}
private static string SetUpLogsDir()
{
string logDir = "";
if (Mode == LaunchMode.Portable)
{
logDir = Path.Combine(BaseDirPath, "Logs");
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
return null;
}
}
else
{
if (OperatingSystem.IsMacOS())
{
// NOTE: Should evaluate to "~/Library/Logs/Ryujinx/".
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Logs", DefaultBaseDir);
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
logDir = "";
}
if (string.IsNullOrEmpty(logDir))
{
// NOTE: Should evaluate to "~/Library/Application Support/Ryujinx/Logs".
logDir = Path.Combine(BaseDirPath, "Logs");
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
return null;
}
}
}
else if (OperatingSystem.IsWindows())
{
// NOTE: Should evaluate to a "Logs" directory in whatever directory Ryujinx was launched from.
logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
logDir = "";
}
if (string.IsNullOrEmpty(logDir))
{
// NOTE: Should evaluate to "C:\Users\user\AppData\Roaming\Ryujinx\Logs".
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs");
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
return null;
}
}
}
else if (OperatingSystem.IsLinux())
{
// NOTE: Should evaluate to "~/.config/Ryujinx/Logs".
logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs");
try
{
Directory.CreateDirectory(logDir);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
return null;
}
}
}
return logDir;
}
private static void SetupBasePaths()
{
Directory.CreateDirectory(BaseDirPath);
LogsDirPath = SetUpLogsDir();
Directory.CreateDirectory(GamesDirPath = Path.Combine(BaseDirPath, GamesDir));
Directory.CreateDirectory(ProfilesDirPath = Path.Combine(BaseDirPath, ProfilesDir));
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
}
// Check if existing old baseDirPath is a symlink, to prevent possible errors.
// Should be removed, when the existance of the old directory isn't checked anymore.
// Should be removed, when the existence of the old directory isn't checked anymore.
private static bool IsPathSymlink(string path)
{
FileAttributes attributes = File.GetAttributes(path);
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
try
{
FileAttributes attributes = File.GetAttributes(path);
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
}
catch
{
return false;
}
}
private static void CopyDirectory(string sourceDir, string destinationDir)
[SupportedOSPlatform("macos")]
public static void FixMacOSConfigurationFolders()
{
var dir = new DirectoryInfo(sourceDir);
if (!dir.Exists)
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
".config", DefaultBaseDir);
if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
{
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath);
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
}
DirectoryInfo[] subDirs = dir.GetDirectories();
Directory.CreateDirectory(destinationDir);
foreach (FileInfo file in dir.GetFiles())
string correctApplicationDataDirectoryPath =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
if (IsPathSymlink(correctApplicationDataDirectoryPath))
{
if (file.Name == ".DS_Store")
//copy the files somewhere temporarily
string tempPath = Path.Combine(Path.GetTempPath(), DefaultBaseDir);
try
{
continue;
FileSystemUtils.CopyDirectory(correctApplicationDataDirectoryPath, tempPath, true);
}
catch (Exception exception)
{
Logger.Error?.Print(LogClass.Application,
$"Critical error copying Ryujinx application data into the temp folder. {exception}");
try
{
FileSystemInfo resolvedDirectoryInfo =
Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true);
string resolvedPath = resolvedDirectoryInfo.FullName;
Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink.");
}
catch (Exception symlinkException)
{
Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder.");
}
return;
}
file.CopyTo(Path.Combine(destinationDir, file.Name));
}
//delete the symlink
try
{
//This will fail if this is an actual directory, so there is no way we can actually delete user data here.
File.Delete(correctApplicationDataDirectoryPath);
}
catch (Exception exception)
{
Logger.Error?.Print(LogClass.Application,
$"Critical error deleting the Ryujinx application data folder symlink at {correctApplicationDataDirectoryPath}. {exception}");
try
{
FileSystemInfo resolvedDirectoryInfo =
Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true);
string resolvedPath = resolvedDirectoryInfo.FullName;
Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink.");
}
catch (Exception symlinkException)
{
Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder.");
}
return;
}
foreach (DirectoryInfo subDir in subDirs)
{
CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name));
//put the files back
try
{
FileSystemUtils.CopyDirectory(tempPath, correctApplicationDataDirectoryPath, true);
}
catch (Exception exception)
{
Logger.Error?.Print(LogClass.Application,
$"Critical error copying Ryujinx application data into the correct location. {exception}. Please manually move your application data from {tempPath} to {correctApplicationDataDirectoryPath}.");
}
}
}

View File

@ -6,7 +6,7 @@ namespace Ryujinx.Common.Configuration.Hid
{
public Key ToggleVsync { get; set; }
public Key Screenshot { get; set; }
public Key ShowUi { get; set; }
public Key ShowUI { get; set; }
public Key Pause { get; set; }
public Key ToggleMute { get; set; }
public Key ResScaleUp { get; set; }

View File

@ -0,0 +1,9 @@
namespace Ryujinx.Common.Configuration
{
public class Mod
{
public string Name { get; set; }
public string Path { get; set; }
public bool Enabled { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Ryujinx.Common.Configuration
{
public struct ModMetadata
{
public List<Mod> Mods { get; set; }
public ModMetadata()
{
Mods = new List<Mod>();
}
}
}

View File

@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(ModMetadata))]
public partial class ModMetadataJsonSerializerContext : JsonSerializerContext
{
}
}

View File

@ -70,7 +70,7 @@ namespace Ryujinx.Common.Logging
ServiceVi,
SurfaceFlinger,
TamperMachine,
Ui,
UI,
Vic,
}
}

View File

@ -3,6 +3,7 @@ using Ryujinx.Common.SystemInterop;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
@ -22,6 +23,9 @@ namespace Ryujinx.Common.Logging
public readonly struct Log
{
private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir).FullName, "[redacted]");
internal readonly LogLevel Level;
internal Log(LogLevel level)
@ -100,7 +104,12 @@ namespace Ryujinx.Common.Logging
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string FormatMessage(LogClass logClass, string caller, string message) => $"{logClass} {caller}: {message}";
private static string FormatMessage(LogClass logClass, string caller, string message)
{
message = message.Replace(_homeDir, _homeDirRedacted);
return $"{logClass} {caller}: {message}";
}
}
public static Log? Debug { get; private set; }

View File

@ -13,31 +13,82 @@ namespace Ryujinx.Common.Logging.Targets
string ILogTarget.Name { get => _name; }
public FileLogTarget(string path, string name)
: this(path, name, FileShare.Read, FileMode.Append)
{ }
public FileLogTarget(string name, FileStream fileStream)
{
_name = name;
_logWriter = new StreamWriter(fileStream);
_formatter = new DefaultLogFormatter();
}
public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode)
public static FileStream PrepareLogFile(string path)
{
// Ensure directory is present
DirectoryInfo logDir = new(Path.Combine(path, "Logs"));
logDir.Create();
DirectoryInfo logDir;
try
{
logDir = new DirectoryInfo(path);
}
catch (ArgumentException exception)
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory path ('{path}') was invalid: {exception}");
return null;
}
try
{
logDir.Create();
}
catch (IOException exception)
{
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}': {exception}");
return null;
}
// Clean up old logs, should only keep 3
FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray();
for (int i = 0; i < files.Length - 2; i++)
{
files[i].Delete();
try
{
files[i].Delete();
}
catch (UnauthorizedAccessException exception)
{
Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
return null;
}
catch (IOException exception)
{
Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
return null;
}
}
string version = ReleaseInformation.GetVersion();
string version = ReleaseInformation.Version;
// Get path for the current time
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
_name = name;
_logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
_formatter = new DefaultLogFormatter();
try
{
return File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
}
catch (UnauthorizedAccessException exception)
{
Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
return null;
}
catch (IOException exception)
{
Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
return null;
}
}
public void Log(object sender, LogEventArgs args)

View File

@ -744,6 +744,17 @@ namespace Ryujinx.Common.Memory
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}
public struct Array65<T> : IArray<T> where T : unmanaged
{
T _e0;
Array64<T> _other;
public readonly int Length => 65;
public ref T this[int index] => ref AsSpan()[index];
[Pure]
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
}
public struct Array73<T> : IArray<T> where T : unmanaged
{
T _e0;

View File

@ -63,6 +63,18 @@ namespace Ryujinx.Common.Memory
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
}
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
public struct ByteArray3000 : IArray<byte>
{
private const int Size = 3000;
byte _element;
public readonly int Length => Size;
public ref byte this[int index] => ref AsSpan()[index];
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
}
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
public struct ByteArray4096 : IArray<byte>
{

View File

@ -1,5 +1,3 @@
using Ryujinx.Common.Configuration;
using System;
using System.Reflection;
namespace Ryujinx.Common
@ -9,50 +7,25 @@ namespace Ryujinx.Common
{
private const string FlatHubChannelOwner = "flathub";
public const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
public const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
private const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
public static bool IsValid()
{
return !BuildGitHash.StartsWith("%%") &&
!ReleaseChannelName.StartsWith("%%") &&
!ReleaseChannelOwner.StartsWith("%%") &&
!ReleaseChannelRepo.StartsWith("%%");
}
public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json";
public static bool IsFlatHubBuild()
{
return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
}
public static bool IsValid =>
!BuildGitHash.StartsWith("%%") &&
!ReleaseChannelName.StartsWith("%%") &&
!ReleaseChannelOwner.StartsWith("%%") &&
!ReleaseChannelRepo.StartsWith("%%") &&
!ConfigFileName.StartsWith("%%");
public static string GetVersion()
{
if (IsValid())
{
return BuildVersion;
}
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
}
#if FORCE_EXTERNAL_BASE_DIR
public static string GetBaseApplicationDirectory()
{
return AppDataManager.BaseDirPath;
}
#else
public static string GetBaseApplicationDirectory()
{
if (IsFlatHubBuild() || OperatingSystem.IsMacOS())
{
return AppDataManager.BaseDirPath;
}
return AppDomain.CurrentDomain.BaseDirectory;
}
#endif
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
}
}

View File

@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Ryujinx.Common.Utilities
{
public static class FileSystemUtils
{
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
{
// Get information about the source directory
var dir = new DirectoryInfo(sourceDir);
// Check if the source directory exists
if (!dir.Exists)
{
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
}
// Cache directories before we start copying
DirectoryInfo[] dirs = dir.GetDirectories();
// Create the destination directory
Directory.CreateDirectory(destinationDir);
// Get the files in the source directory and copy to the destination directory
foreach (FileInfo file in dir.GetFiles())
{
string targetFilePath = Path.Combine(destinationDir, file.Name);
file.CopyTo(targetFilePath);
}
// If recursive and copying subdirectories, recursively call this method
if (recursive)
{
foreach (DirectoryInfo subDir in dirs)
{
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
CopyDirectory(subDir.FullName, newDestinationDir, true);
}
}
}
public static void MoveDirectory(string sourceDir, string destinationDir)
{
CopyDirectory(sourceDir, destinationDir, true);
Directory.Delete(sourceDir, true);
}
public static string SanitizeFileName(string fileName)
{
var reservedChars = new HashSet<char>(Path.GetInvalidFileNameChars());
return string.Concat(fileName.Select(c => reservedChars.Contains(c) ? '_' : c));
}
}
}

View File

@ -8,7 +8,6 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Cpu.AppleHv
{
@ -16,31 +15,10 @@ namespace Ryujinx.Cpu.AppleHv
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
/// </summary>
[SupportedOSPlatform("macos")]
public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
public class HvMemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
private enum HostMappedPtBits : ulong
{
Unmapped = 0,
Mapped,
WriteTracked,
ReadWriteTracked,
MappedReplicated = 0x5555555555555555,
WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
ReadWriteTrackedReplicated = ulong.MaxValue,
}
private readonly InvalidAccessHandler _invalidAccessHandler;
private readonly ulong _addressSpaceSize;
private readonly HvAddressSpace _addressSpace;
internal HvAddressSpace AddressSpace => _addressSpace;
@ -48,7 +26,7 @@ namespace Ryujinx.Cpu.AppleHv
private readonly MemoryBlock _backingMemory;
private readonly PageTable<ulong> _pageTable;
private readonly ulong[] _pageBitmap;
private readonly ManagedPageFlags _pages;
public bool Supports4KBPages => true;
@ -62,6 +40,8 @@ namespace Ryujinx.Cpu.AppleHv
public event Action<ulong, ulong> UnmapEvent;
protected override ulong AddressSpaceSize { get; }
/// <summary>
/// Creates a new instance of the Hypervisor memory manager.
/// </summary>
@ -73,7 +53,7 @@ namespace Ryujinx.Cpu.AppleHv
_backingMemory = backingMemory;
_pageTable = new PageTable<ulong>();
_invalidAccessHandler = invalidAccessHandler;
_addressSpaceSize = addressSpaceSize;
AddressSpaceSize = addressSpaceSize;
ulong asSize = PageSize;
int asBits = PageBits;
@ -88,46 +68,10 @@ namespace Ryujinx.Cpu.AppleHv
AddressSpaceBits = asBits;
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
_pages = new ManagedPageFlags(AddressSpaceBits);
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
}
/// <summary>
/// Checks if the virtual address is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address</param>
/// <returns>True if the virtual address is part of the addressable space</returns>
private bool ValidateAddress(ulong va)
{
return va < _addressSpaceSize;
}
/// <summary>
/// Checks if the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
private bool ValidateAddressAndSize(ulong va, ulong size)
{
ulong endVa = va + size;
return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
}
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
private void AssertValidAddressAndSize(ulong va, ulong size)
{
if (!ValidateAddressAndSize(va, size))
{
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
}
}
/// <inheritdoc/>
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
{
@ -135,7 +79,7 @@ namespace Ryujinx.Cpu.AppleHv
PtMap(va, pa, size);
_addressSpace.MapUser(va, pa, size, MemoryPermission.ReadWriteExecute);
AddMapping(va, size);
_pages.AddMapping(va, size);
Tracking.Map(va, size);
}
@ -166,7 +110,7 @@ namespace Ryujinx.Cpu.AppleHv
UnmapEvent?.Invoke(va, size);
Tracking.Unmap(va, size);
RemoveMapping(va, size);
_pages.RemoveMapping(va, size);
_addressSpace.UnmapUser(va, size);
PtUnmap(va, size);
}
@ -209,9 +153,19 @@ namespace Ryujinx.Cpu.AppleHv
}
/// <inheritdoc/>
public void Read(ulong va, Span<byte> data)
public override void Read(ulong va, Span<byte> data)
{
ReadImpl(va, data);
try
{
base.Read(va, data);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
/// <inheritdoc/>
@ -340,7 +294,7 @@ namespace Ryujinx.Cpu.AppleHv
{
Span<byte> data = new byte[size];
ReadImpl(va, data);
base.Read(va, data);
return data;
}
@ -367,7 +321,7 @@ namespace Ryujinx.Cpu.AppleHv
{
Memory<byte> memory = new byte[size];
ReadImpl(va, memory.Span);
base.Read(va, memory.Span);
return new WritableRegion(this, va, memory);
}
@ -390,22 +344,7 @@ namespace Ryujinx.Cpu.AppleHv
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va)
{
return ValidateAddress(va) && IsMappedImpl(va);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsMappedImpl(ulong va)
{
ulong page = va >> PageBits;
int bit = (int)((page & 31) << 1);
int pageIndex = (int)(page >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
return ((pte >> bit) & 3) != 0;
return ValidateAddress(va) && _pages.IsMapped(va);
}
/// <inheritdoc/>
@ -413,58 +352,7 @@ namespace Ryujinx.Cpu.AppleHv
{
AssertValidAddressAndSize(va, size);
return IsRangeMappedImpl(va, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
{
startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
pageIndex = (int)(pageStart >> PageToPteShift);
pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
}
private bool IsRangeMappedImpl(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
if (pages == 1)
{
return IsMappedImpl(va);
}
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
// Check if either bit in each 2 bit page entry is set.
// OR the block with itself shifted down by 1, and check the first bit of each entry.
ulong mask = BlockMappedMask & startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
pte |= pte >> 1;
if ((pte & mask) != mask)
{
return false;
}
mask = BlockMappedMask;
}
return true;
return _pages.IsRangeMapped(va, size);
}
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
@ -576,48 +464,6 @@ namespace Ryujinx.Cpu.AppleHv
return regions;
}
private void ReadImpl(ulong va, Span<byte> data)
{
if (data.Length == 0)
{
return;
}
try
{
AssertValidAddressAndSize(va, (ulong)data.Length);
int offset = 0, size;
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressChecked(va);
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
_backingMemory.GetSpan(pa, size).CopyTo(data[..size]);
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = GetPhysicalAddressChecked(va + (ulong)offset);
size = Math.Min(data.Length - offset, PageSize);
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
}
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
/// <inheritdoc/>
/// <remarks>
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
@ -632,76 +478,7 @@ namespace Ryujinx.Cpu.AppleHv
return;
}
// Software table, used for managed memory tracking.
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
if (pages == 1)
{
ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked);
int bit = (int)((pageStart & 31) << 1);
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
ulong state = ((pte >> bit) & 3);
if (state >= tag)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
return;
}
else if (state == 0)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
ulong mappedMask = mask & BlockMappedMask;
ulong mappedPte = pte | (pte >> 1);
if ((mappedPte & mappedMask) != mappedMask)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
pte &= mask;
if ((pte & anyTrackingTag) != 0) // Search for any tracking.
{
// Writes trigger any tracking.
// Only trigger tracking from reads if both bits are set on any page.
if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
break;
}
}
mask = ulong.MaxValue;
}
}
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
}
/// <summary>
@ -728,103 +505,28 @@ namespace Ryujinx.Cpu.AppleHv
}
/// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
{
// Protection is inverted on software pages, since the default value is 0.
protection = (~protection) & MemoryPermission.ReadAndWrite;
int pages = GetPagesCount(va, size, out va);
ulong pageStart = va >> PageBits;
if (pages == 1)
if (guest)
{
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)HostMappedPtBits.Mapped,
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked,
_ => (ulong)HostMappedPtBits.ReadWriteTracked,
};
int bit = (int)((pageStart & 31) << 1);
ulong tagMask = 3UL << bit;
ulong invTagMask = ~tagMask;
ulong tag = protTag << bit;
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
_addressSpace.ReprotectUser(va, size, protection);
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated,
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated,
_ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated,
};
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Change the protection of all 2 bit entries that are mapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask &= mask; // Only update mapped pages within the given range.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
mask = ulong.MaxValue;
}
_pages.TrackingReprotect(va, size, protection);
}
protection = protection switch
{
MemoryPermission.None => MemoryPermission.ReadAndWrite,
MemoryPermission.Write => MemoryPermission.Read,
_ => MemoryPermission.None,
};
_addressSpace.ReprotectUser(va, size, protection);
}
/// <inheritdoc/>
public RegionHandle BeginTracking(ulong address, ulong size, int id)
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
{
return Tracking.BeginTracking(address, size, id);
return Tracking.BeginTracking(address, size, id, flags);
}
/// <inheritdoc/>
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
{
return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
}
/// <inheritdoc/>
@ -833,86 +535,6 @@ namespace Ryujinx.Cpu.AppleHv
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
}
/// <summary>
/// Adds the given address mapping to the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be mapped</param>
private void AddMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Map all 2-bit entries that are unmapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
mask = ulong.MaxValue;
}
}
/// <summary>
/// Removes the given address mapping from the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be unmapped</param>
private void RemoveMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
startMask = ~startMask;
endMask = ~endMask;
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask |= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
mask = 0;
}
}
private ulong GetPhysicalAddressChecked(ulong va)
{
if (!IsMapped(va))
@ -936,6 +558,10 @@ namespace Ryujinx.Cpu.AppleHv
_addressSpace.Dispose();
}
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
=> _backingMemory.GetSpan(pa, size);
protected override ulong TranslateVirtualAddressForRead(ulong va)
=> GetPhysicalAddressChecked(va);
}
}

View File

@ -28,8 +28,9 @@ namespace Ryujinx.Cpu
/// <param name="address">CPU virtual address of the region</param>
/// <param name="size">Size of the region</param>
/// <param name="id">Handle ID</param>
/// <param name="flags">Region flags</param>
/// <returns>The memory tracking handle</returns>
RegionHandle BeginTracking(ulong address, ulong size, int id);
RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None);
/// <summary>
/// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
@ -39,8 +40,9 @@ namespace Ryujinx.Cpu
/// <param name="handles">Handles to inherit state from or reuse. When none are present, provide null</param>
/// <param name="granularity">Desired granularity of write tracking</param>
/// <param name="id">Handle ID</param>
/// <param name="flags">Region flags</param>
/// <returns>The memory tracking handle</returns>
MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id);
MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None);
/// <summary>
/// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.

View File

@ -14,12 +14,8 @@ namespace Ryujinx.Cpu.Jit
/// <summary>
/// Represents a CPU memory manager.
/// </summary>
public sealed class MemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
private const int PteSize = 8;
private const int PointerTagBit = 62;
@ -35,10 +31,10 @@ namespace Ryujinx.Cpu.Jit
/// </summary>
public int AddressSpaceBits { get; }
private readonly ulong _addressSpaceSize;
private readonly MemoryBlock _pageTable;
private readonly ManagedPageFlags _pages;
/// <summary>
/// Page table base pointer.
/// </summary>
@ -50,6 +46,8 @@ namespace Ryujinx.Cpu.Jit
public event Action<ulong, ulong> UnmapEvent;
protected override ulong AddressSpaceSize { get; }
/// <summary>
/// Creates a new instance of the memory manager.
/// </summary>
@ -71,9 +69,11 @@ namespace Ryujinx.Cpu.Jit
}
AddressSpaceBits = asBits;
_addressSpaceSize = asSize;
AddressSpaceSize = asSize;
_pageTable = new MemoryBlock((asSize / PageSize) * PteSize);
_pages = new ManagedPageFlags(AddressSpaceBits);
Tracking = new MemoryTracking(this, PageSize);
}
@ -93,6 +93,7 @@ namespace Ryujinx.Cpu.Jit
remainingSize -= PageSize;
}
_pages.AddMapping(oVa, size);
Tracking.Map(oVa, size);
}
@ -115,6 +116,7 @@ namespace Ryujinx.Cpu.Jit
UnmapEvent?.Invoke(va, size);
Tracking.Unmap(va, size);
_pages.RemoveMapping(va, size);
ulong remainingSize = size;
while (remainingSize != 0)
@ -153,9 +155,39 @@ namespace Ryujinx.Cpu.Jit
}
/// <inheritdoc/>
public void Read(ulong va, Span<byte> data)
public T ReadGuest<T>(ulong va) where T : unmanaged
{
ReadImpl(va, data);
try
{
SignalMemoryTrackingImpl(va, (ulong)Unsafe.SizeOf<T>(), false, true);
return Read<T>(va);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
return default;
}
}
/// <inheritdoc/>
public override void Read(ulong va, Span<byte> data)
{
try
{
base.Read(va, data);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
/// <inheritdoc/>
@ -177,6 +209,16 @@ namespace Ryujinx.Cpu.Jit
WriteImpl(va, data);
}
/// <inheritdoc/>
public void WriteGuest<T>(ulong va, T value) where T : unmanaged
{
Span<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1));
SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true);
WriteImpl(va, data);
}
/// <inheritdoc/>
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
{
@ -290,7 +332,7 @@ namespace Ryujinx.Cpu.Jit
{
Span<byte> data = new byte[size];
ReadImpl(va, data);
base.Read(va, data);
return data;
}
@ -462,48 +504,6 @@ namespace Ryujinx.Cpu.Jit
return regions;
}
private void ReadImpl(ulong va, Span<byte> data)
{
if (data.Length == 0)
{
return;
}
try
{
AssertValidAddressAndSize(va, (ulong)data.Length);
int offset = 0, size;
if ((va & PageMask) != 0)
{
ulong pa = GetPhysicalAddressInternal(va);
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
_backingMemory.GetSpan(pa, size).CopyTo(data[..size]);
offset += size;
}
for (; offset < data.Length; offset += size)
{
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
size = Math.Min(data.Length - offset, PageSize);
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
}
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
/// <inheritdoc/>
public bool IsRangeMapped(ulong va, ulong size)
{
@ -544,37 +544,6 @@ namespace Ryujinx.Cpu.Jit
return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0;
}
private bool ValidateAddress(ulong va)
{
return va < _addressSpaceSize;
}
/// <summary>
/// Checks if the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
private bool ValidateAddressAndSize(ulong va, ulong size)
{
ulong endVa = va + size;
return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
}
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
private void AssertValidAddressAndSize(ulong va, ulong size)
{
if (!ValidateAddressAndSize(va, size))
{
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
}
}
private ulong GetPhysicalAddressInternal(ulong va)
{
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
@ -587,50 +556,57 @@ namespace Ryujinx.Cpu.Jit
}
/// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
{
AssertValidAddressAndSize(va, size);
// Protection is inverted on software pages, since the default value is 0.
protection = (~protection) & MemoryPermission.ReadAndWrite;
long tag = protection switch
if (guest)
{
MemoryPermission.None => 0L,
MemoryPermission.Write => 2L << PointerTagBit,
_ => 3L << PointerTagBit,
};
// Protection is inverted on software pages, since the default value is 0.
protection = (~protection) & MemoryPermission.ReadAndWrite;
int pages = GetPagesCount(va, (uint)size, out va);
ulong pageStart = va >> PageBits;
long invTagMask = ~(0xffffL << 48);
for (int page = 0; page < pages; page++)
{
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
long pte;
do
long tag = protection switch
{
pte = Volatile.Read(ref pageRef);
}
while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
MemoryPermission.None => 0L,
MemoryPermission.Write => 2L << PointerTagBit,
_ => 3L << PointerTagBit,
};
pageStart++;
int pages = GetPagesCount(va, (uint)size, out va);
ulong pageStart = va >> PageBits;
long invTagMask = ~(0xffffL << 48);
for (int page = 0; page < pages; page++)
{
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
long pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
pageStart++;
}
}
else
{
_pages.TrackingReprotect(va, size, protection);
}
}
/// <inheritdoc/>
public RegionHandle BeginTracking(ulong address, ulong size, int id)
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
{
return Tracking.BeginTracking(address, size, id);
return Tracking.BeginTracking(address, size, id, flags);
}
/// <inheritdoc/>
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
{
return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
}
/// <inheritdoc/>
@ -639,8 +615,7 @@ namespace Ryujinx.Cpu.Jit
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
}
/// <inheritdoc/>
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
private void SignalMemoryTrackingImpl(ulong va, ulong size, bool write, bool guest, bool precise = false, int? exemptId = null)
{
AssertValidAddressAndSize(va, size);
@ -650,31 +625,47 @@ namespace Ryujinx.Cpu.Jit
return;
}
// We emulate guard pages for software memory access. This makes for an easy transition to
// tracking using host guard pages in future, but also supporting platforms where this is not possible.
// If the memory tracking is coming from the guest, use the tag bits in the page table entry.
// Otherwise, use the managed page flags.
// Write tag includes read protection, since we don't have any read actions that aren't performed before write too.
long tag = (write ? 3L : 1L) << PointerTagBit;
int pages = GetPagesCount(va, (uint)size, out _);
ulong pageStart = va >> PageBits;
for (int page = 0; page < pages; page++)
if (guest)
{
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
// We emulate guard pages for software memory access. This makes for an easy transition to
// tracking using host guard pages in future, but also supporting platforms where this is not possible.
long pte;
// Write tag includes read protection, since we don't have any read actions that aren't performed before write too.
long tag = (write ? 3L : 1L) << PointerTagBit;
pte = Volatile.Read(ref pageRef);
int pages = GetPagesCount(va, (uint)size, out _);
ulong pageStart = va >> PageBits;
if ((pte & tag) != 0)
for (int page = 0; page < pages; page++)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
break;
}
ref long pageRef = ref _pageTable.GetRef<long>(pageStart * PteSize);
pageStart++;
long pte;
pte = Volatile.Read(ref pageRef);
if ((pte & tag) != 0)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId, true);
break;
}
pageStart++;
}
}
else
{
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
}
}
/// <inheritdoc/>
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
{
SignalMemoryTrackingImpl(va, size, write, false, precise, exemptId);
}
private ulong PaToPte(ulong pa)
@ -691,5 +682,11 @@ namespace Ryujinx.Cpu.Jit
/// Disposes of resources used by the memory manager.
/// </summary>
protected override void Destroy() => _pageTable.Dispose();
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
=> _backingMemory.GetSpan(pa, size);
protected override ulong TranslateVirtualAddressForRead(ulong va)
=> GetPhysicalAddressInternal(va);
}
}

View File

@ -6,46 +6,24 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Ryujinx.Cpu.Jit
{
/// <summary>
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
/// </summary>
public sealed class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
private enum HostMappedPtBits : ulong
{
Unmapped = 0,
Mapped,
WriteTracked,
ReadWriteTracked,
MappedReplicated = 0x5555555555555555,
WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
ReadWriteTrackedReplicated = ulong.MaxValue,
}
private readonly InvalidAccessHandler _invalidAccessHandler;
private readonly bool _unsafeMode;
private readonly AddressSpace _addressSpace;
public ulong AddressSpaceSize { get; }
private readonly PageTable<ulong> _pageTable;
private readonly MemoryEhMeilleure _memoryEh;
private readonly ulong[] _pageBitmap;
private readonly ManagedPageFlags _pages;
/// <inheritdoc/>
public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize;
@ -60,6 +38,8 @@ namespace Ryujinx.Cpu.Jit
public event Action<ulong, ulong> UnmapEvent;
protected override ulong AddressSpaceSize { get; }
/// <summary>
/// Creates a new instance of the host mapped memory manager.
/// </summary>
@ -85,48 +65,12 @@ namespace Ryujinx.Cpu.Jit
AddressSpaceBits = asBits;
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
_pages = new ManagedPageFlags(AddressSpaceBits);
Tracking = new MemoryTracking(this, (int)MemoryBlock.GetPageSize(), invalidAccessHandler);
_memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking);
}
/// <summary>
/// Checks if the virtual address is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address</param>
/// <returns>True if the virtual address is part of the addressable space</returns>
private bool ValidateAddress(ulong va)
{
return va < AddressSpaceSize;
}
/// <summary>
/// Checks if the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
private bool ValidateAddressAndSize(ulong va, ulong size)
{
ulong endVa = va + size;
return endVa >= va && endVa >= size && endVa <= AddressSpaceSize;
}
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
private void AssertValidAddressAndSize(ulong va, ulong size)
{
if (!ValidateAddressAndSize(va, size))
{
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
}
}
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
/// </summary>
@ -134,7 +78,7 @@ namespace Ryujinx.Cpu.Jit
/// <param name="size">Size of the range in bytes</param>
private void AssertMapped(ulong va, ulong size)
{
if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size))
if (!ValidateAddressAndSize(va, size) || !_pages.IsRangeMapped(va, size))
{
throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
@ -146,7 +90,7 @@ namespace Ryujinx.Cpu.Jit
AssertValidAddressAndSize(va, size);
_addressSpace.Map(va, pa, size, flags);
AddMapping(va, size);
_pages.AddMapping(va, size);
PtMap(va, pa, size);
Tracking.Map(va, size);
@ -166,7 +110,7 @@ namespace Ryujinx.Cpu.Jit
UnmapEvent?.Invoke(va, size);
Tracking.Unmap(va, size);
RemoveMapping(va, size);
_pages.RemoveMapping(va, size);
PtUnmap(va, size);
_addressSpace.Unmap(va, size);
}
@ -235,7 +179,7 @@ namespace Ryujinx.Cpu.Jit
}
/// <inheritdoc/>
public void Read(ulong va, Span<byte> data)
public override void Read(ulong va, Span<byte> data)
{
try
{
@ -377,22 +321,7 @@ namespace Ryujinx.Cpu.Jit
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va)
{
return ValidateAddress(va) && IsMappedImpl(va);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsMappedImpl(ulong va)
{
ulong page = va >> PageBits;
int bit = (int)((page & 31) << 1);
int pageIndex = (int)(page >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
return ((pte >> bit) & 3) != 0;
return ValidateAddress(va) && _pages.IsMapped(va);
}
/// <inheritdoc/>
@ -400,58 +329,7 @@ namespace Ryujinx.Cpu.Jit
{
AssertValidAddressAndSize(va, size);
return IsRangeMappedImpl(va, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
{
startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
pageIndex = (int)(pageStart >> PageToPteShift);
pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
}
private bool IsRangeMappedImpl(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
if (pages == 1)
{
return IsMappedImpl(va);
}
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
// Check if either bit in each 2 bit page entry is set.
// OR the block with itself shifted down by 1, and check the first bit of each entry.
ulong mask = BlockMappedMask & startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
pte |= pte >> 1;
if ((pte & mask) != mask)
{
return false;
}
mask = BlockMappedMask;
}
return true;
return _pages.IsRangeMapped(va, size);
}
/// <inheritdoc/>
@ -526,76 +404,7 @@ namespace Ryujinx.Cpu.Jit
return;
}
// Software table, used for managed memory tracking.
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
if (pages == 1)
{
ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked);
int bit = (int)((pageStart & 31) << 1);
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
ulong state = ((pte >> bit) & 3);
if (state >= tag)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
return;
}
else if (state == 0)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
ulong mappedMask = mask & BlockMappedMask;
ulong mappedPte = pte | (pte >> 1);
if ((mappedPte & mappedMask) != mappedMask)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
pte &= mask;
if ((pte & anyTrackingTag) != 0) // Search for any tracking.
{
// Writes trigger any tracking.
// Only trigger tracking from reads if both bits are set on any page.
if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
break;
}
}
mask = ulong.MaxValue;
}
}
_pages.SignalMemoryTracking(Tracking, va, size, write, exemptId);
}
/// <summary>
@ -622,103 +431,28 @@ namespace Ryujinx.Cpu.Jit
}
/// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest)
{
// Protection is inverted on software pages, since the default value is 0.
protection = (~protection) & MemoryPermission.ReadAndWrite;
int pages = GetPagesCount(va, size, out va);
ulong pageStart = va >> PageBits;
if (pages == 1)
if (guest)
{
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)HostMappedPtBits.Mapped,
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked,
_ => (ulong)HostMappedPtBits.ReadWriteTracked,
};
int bit = (int)((pageStart & 31) << 1);
ulong tagMask = 3UL << bit;
ulong invTagMask = ~tagMask;
ulong tag = protTag << bit;
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
_addressSpace.Base.Reprotect(va, size, protection, false);
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated,
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated,
_ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated,
};
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Change the protection of all 2 bit entries that are mapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask &= mask; // Only update mapped pages within the given range.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
mask = ulong.MaxValue;
}
_pages.TrackingReprotect(va, size, protection);
}
protection = protection switch
{
MemoryPermission.None => MemoryPermission.ReadAndWrite,
MemoryPermission.Write => MemoryPermission.Read,
_ => MemoryPermission.None,
};
_addressSpace.Base.Reprotect(va, size, protection, false);
}
/// <inheritdoc/>
public RegionHandle BeginTracking(ulong address, ulong size, int id)
public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None)
{
return Tracking.BeginTracking(address, size, id);
return Tracking.BeginTracking(address, size, id, flags);
}
/// <inheritdoc/>
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None)
{
return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags);
}
/// <inheritdoc/>
@ -727,86 +461,6 @@ namespace Ryujinx.Cpu.Jit
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
}
/// <summary>
/// Adds the given address mapping to the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be mapped</param>
private void AddMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Map all 2-bit entries that are unmapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
mask = ulong.MaxValue;
}
}
/// <summary>
/// Removes the given address mapping from the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be unmapped</param>
private void RemoveMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
startMask = ~startMask;
endMask = ~endMask;
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask |= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
mask = 0;
}
}
/// <summary>
/// Disposes of resources used by the memory manager.
/// </summary>
@ -816,6 +470,10 @@ namespace Ryujinx.Cpu.Jit
_memoryEh.Dispose();
}
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
=> _addressSpace.Mirror.GetSpan(pa, size);
protected override ulong TranslateVirtualAddressForRead(ulong va)
=> va;
}
}

View File

@ -10,6 +10,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
public readonly List<InstInfo> Instructions;
public readonly bool EndsWithBranch;
public readonly bool HasHostCall;
public readonly bool HasHostCallSkipContext;
public readonly bool IsTruncated;
public readonly bool IsLoopEnd;
public readonly bool IsThumb;
@ -20,6 +21,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
List<InstInfo> instructions,
bool endsWithBranch,
bool hasHostCall,
bool hasHostCallSkipContext,
bool isTruncated,
bool isLoopEnd,
bool isThumb)
@ -31,6 +33,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
Instructions = instructions;
EndsWithBranch = endsWithBranch;
HasHostCall = hasHostCall;
HasHostCallSkipContext = hasHostCallSkipContext;
IsTruncated = isTruncated;
IsLoopEnd = isLoopEnd;
IsThumb = isThumb;
@ -57,6 +60,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
Instructions.GetRange(0, splitIndex),
false,
HasHostCall,
HasHostCallSkipContext,
false,
false,
IsThumb);
@ -67,6 +71,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
Instructions.GetRange(splitIndex, splitCount),
EndsWithBranch,
HasHostCall,
HasHostCallSkipContext,
IsTruncated,
IsLoopEnd,
IsThumb);

View File

@ -208,6 +208,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
InstMeta meta;
InstFlags extraFlags = InstFlags.None;
bool hasHostCall = false;
bool hasHostCallSkipContext = false;
bool isTruncated = false;
do
@ -246,9 +247,17 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
meta = InstTableA32<T>.GetMeta(encoding, cpuPreset.Version, cpuPreset.Features);
}
if (meta.Name.IsSystemOrCall() && !hasHostCall)
if (meta.Name.IsSystemOrCall())
{
hasHostCall = meta.Name.IsCall() || InstEmitSystem.NeedsCall(meta.Name);
if (!hasHostCall)
{
hasHostCall = InstEmitSystem.NeedsCall(meta.Name);
}
if (!hasHostCallSkipContext)
{
hasHostCallSkipContext = meta.Name.IsCall() || InstEmitSystem.NeedsCallSkipContext(meta.Name);
}
}
insts.Add(new(encoding, meta.Name, meta.EmitFunc, meta.Flags | extraFlags));
@ -259,8 +268,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
if (!isTruncated && IsBackwardsBranch(meta.Name, encoding))
{
hasHostCall = true;
isLoopEnd = true;
hasHostCallSkipContext = true;
}
return new(
@ -269,6 +278,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
insts,
!isTruncated,
hasHostCall,
hasHostCallSkipContext,
isTruncated,
isLoopEnd,
isThumb);

View File

@ -6,6 +6,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
{
public readonly List<Block> Blocks;
public readonly bool HasHostCall;
public readonly bool HasHostCallSkipContext;
public readonly bool IsTruncated;
public MultiBlock(List<Block> blocks)
@ -15,12 +16,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
Block block = blocks[0];
HasHostCall = block.HasHostCall;
HasHostCallSkipContext = block.HasHostCallSkipContext;
for (int index = 1; index < blocks.Count; index++)
{
block = blocks[index];
HasHostCall |= block.HasHostCall;
HasHostCallSkipContext |= block.HasHostCallSkipContext;
}
block = blocks[^1];

View File

@ -106,6 +106,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
if ((regMask & AbiConstants.ReservedRegsMask) == 0)
{
_gprMask |= regMask;
UsedGprsMask |= regMask;
return firstCalleeSaved;
}

View File

@ -305,12 +305,23 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
ForceConditionalEnd(cgContext, ref lastCondition, lastConditionIp);
}
int reservedStackSize = 0;
if (multiBlock.HasHostCall)
{
reservedStackSize = CalculateStackSizeForCallSpill(regAlloc.UsedGprsMask, regAlloc.UsedFpSimdMask, UsablePStateMask);
}
else if (multiBlock.HasHostCallSkipContext)
{
reservedStackSize = 2 * sizeof(ulong); // Context and page table pointers.
}
RegisterSaveRestore rsr = new(
regAlloc.UsedGprsMask & AbiConstants.GprCalleeSavedRegsMask,
regAlloc.UsedFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask,
OperandType.FP64,
multiBlock.HasHostCall,
multiBlock.HasHostCall ? CalculateStackSizeForCallSpill(regAlloc.UsedGprsMask, regAlloc.UsedFpSimdMask, UsablePStateMask) : 0);
multiBlock.HasHostCall || multiBlock.HasHostCallSkipContext,
reservedStackSize);
TailMerger tailMerger = new();
@ -596,7 +607,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
name == InstName.Ldm ||
name == InstName.Ldmda ||
name == InstName.Ldmdb ||
name == InstName.Ldmib)
name == InstName.Ldmib ||
name == InstName.Pop)
{
// Arm32 does not have a return instruction, instead returns are implemented
// either using BX LR (for leaf functions), or POP { ... PC }.
@ -711,7 +723,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
switch (type)
{
case BranchType.SyncPoint:
InstEmitSystem.WriteSyncPoint(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset());
InstEmitSystem.WriteSyncPoint(
context.Writer,
ref asm,
context.RegisterAllocator,
context.TailMerger,
context.GetReservedStackOffset(),
context.StoreToContext,
context.LoadFromContext);
break;
case BranchType.SoftwareInterrupt:
context.StoreToContext();

View File

@ -199,12 +199,12 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
}
}
private static void WriteSpillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
public static void WriteSpillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
{
WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: true);
}
private static void WriteFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
public static void WriteFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
{
WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: false);
}

View File

@ -354,11 +354,18 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
// All instructions that might do a host call should be included here.
// That is required to reserve space on the stack for caller saved registers.
return name == InstName.Mrrc;
}
public static bool NeedsCallSkipContext(InstName name)
{
// All instructions that might do a host call should be included here.
// That is required to reserve space on the stack for caller saved registers.
switch (name)
{
case InstName.Mcr:
case InstName.Mrc:
case InstName.Mrrc:
case InstName.Svc:
case InstName.Udf:
return true;
@ -372,7 +379,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
Assembler asm = new(writer);
WriteCall(ref asm, regAlloc, GetBkptHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm);
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset);
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset);
}
public static void WriteSvc(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint svcId)
@ -380,7 +387,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
Assembler asm = new(writer);
WriteCall(ref asm, regAlloc, GetSvcHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, svcId);
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset);
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset);
}
public static void WriteUdf(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint imm)
@ -388,7 +395,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
Assembler asm = new(writer);
WriteCall(ref asm, regAlloc, GetUdfHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm);
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset);
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset);
}
public static void WriteReadCntpct(CodeWriter writer, RegisterAllocator regAlloc, int spillBaseOffset, int rt, int rt2)
@ -422,14 +429,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
WriteFill(ref asm, regAlloc, resultMask, skipContext: false, spillBaseOffset, tempRegister);
}
public static void WriteSyncPoint(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset)
{
Assembler asm = new(writer);
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: false, spillBaseOffset);
}
private static void WriteSyncPoint(CodeWriter writer, ref Assembler asm, RegisterAllocator regAlloc, TailMerger tailMerger, bool skipContext, int spillBaseOffset)
public static void WriteSyncPoint(
CodeWriter writer,
ref Assembler asm,
RegisterAllocator regAlloc,
TailMerger tailMerger,
int spillBaseOffset,
Action storeToContext = null,
Action loadFromContext = null)
{
int tempRegister = regAlloc.AllocateTempGprRegister();
@ -440,7 +447,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
int branchIndex = writer.InstructionPointer;
asm.Cbnz(rt, 0);
WriteSpill(ref asm, regAlloc, 1u << tempRegister, skipContext, spillBaseOffset, tempRegister);
storeToContext?.Invoke();
WriteSpill(ref asm, regAlloc, 1u << tempRegister, skipContext: true, spillBaseOffset, tempRegister);
Operand rn = Register(tempRegister == 0 ? 1 : 0);
@ -449,7 +457,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
tailMerger.AddConditionalZeroReturn(writer, asm, Register(0, OperandType.I32));
WriteFill(ref asm, regAlloc, 1u << tempRegister, skipContext, spillBaseOffset, tempRegister);
WriteFill(ref asm, regAlloc, 1u << tempRegister, skipContext: true, spillBaseOffset, tempRegister);
loadFromContext?.Invoke();
asm.LdrRiUn(rt, Register(regAlloc.FixedContextRegister), NativeContextOffsets.CounterOffset);
@ -514,18 +523,31 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
private static void WriteSpill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister)
{
WriteSpillOrFill(ref asm, regAlloc, skipContext, exceptMask, spillOffset, tempRegister, spill: true);
if (skipContext)
{
InstEmitFlow.WriteSpillSkipContext(ref asm, regAlloc, spillOffset);
}
else
{
WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: true);
}
}
private static void WriteFill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister)
{
WriteSpillOrFill(ref asm, regAlloc, skipContext, exceptMask, spillOffset, tempRegister, spill: false);
if (skipContext)
{
InstEmitFlow.WriteFillSkipContext(ref asm, regAlloc, spillOffset);
}
else
{
WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: false);
}
}
private static void WriteSpillOrFill(
ref Assembler asm,
RegisterAllocator regAlloc,
bool skipContext,
uint exceptMask,
int spillOffset,
int tempRegister,
@ -533,11 +555,6 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
{
uint gprMask = regAlloc.UsedGprsMask & ~(AbiConstants.GprCalleeSavedRegsMask | exceptMask);
if (skipContext)
{
gprMask &= ~Compiler.UsableGprsMask;
}
if (!spill)
{
// We must reload the status register before reloading the GPRs,
@ -600,11 +617,6 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
uint fpSimdMask = regAlloc.UsedFpSimdMask;
if (skipContext)
{
fpSimdMask &= ~Compiler.UsableFpSimdMask;
}
while (fpSimdMask != 0)
{
int reg = BitOperations.TrailingZeroCount(fpSimdMask);

View File

@ -1106,6 +1106,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
case InstName.Mrs:
case InstName.MsrImm:
case InstName.MsrReg:
case InstName.Sysl:
return true;
}

View File

@ -0,0 +1,48 @@
using System.Diagnostics;
namespace Ryujinx.Cpu.LightningJit.Arm64
{
static class SysUtils
{
public static (uint, uint, uint, uint) UnpackOp1CRnCRmOp2(uint encoding)
{
uint op1 = (encoding >> 16) & 7;
uint crn = (encoding >> 12) & 0xf;
uint crm = (encoding >> 8) & 0xf;
uint op2 = (encoding >> 5) & 7;
return (op1, crn, crm, op2);
}
public static bool IsCacheInstEl0(uint encoding)
{
(uint op1, uint crn, uint crm, uint op2) = UnpackOp1CRnCRmOp2(encoding);
return ((op1 << 11) | (crn << 7) | (crm << 3) | op2) switch
{
0b011_0111_0100_001 => true, // DC ZVA
0b011_0111_1010_001 => true, // DC CVAC
0b011_0111_1100_001 => true, // DC CVAP
0b011_0111_1011_001 => true, // DC CVAU
0b011_0111_1110_001 => true, // DC CIVAC
0b011_0111_0101_001 => true, // IC IVAU
_ => false,
};
}
public static bool IsCacheInstUciTrapped(uint encoding)
{
(uint op1, uint crn, uint crm, uint op2) = UnpackOp1CRnCRmOp2(encoding);
return ((op1 << 11) | (crn << 7) | (crm << 3) | op2) switch
{
0b011_0111_1010_001 => true, // DC CVAC
0b011_0111_1100_001 => true, // DC CVAP
0b011_0111_1011_001 => true, // DC CVAU
0b011_0111_1110_001 => true, // DC CIVAC
0b011_0111_0101_001 => true, // IC IVAU
_ => false,
};
}
}
}

View File

@ -8,7 +8,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
{
static class Decoder
{
private const int MaxInstructionsPerBlock = 1000;
private const int MaxInstructionsPerFunction = 10000;
private const uint NzcvFlags = 0xfu << 28;
private const uint CFlag = 0x1u << 29;
@ -22,10 +22,11 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
bool hasHostCall = false;
bool hasMemoryInstruction = false;
int totalInsts = 0;
while (true)
{
Block block = Decode(cpuPreset, memoryManager, address, ref useMask, ref hasHostCall, ref hasMemoryInstruction);
Block block = Decode(cpuPreset, memoryManager, address, ref totalInsts, ref useMask, ref hasHostCall, ref hasMemoryInstruction);
if (!block.IsTruncated && TryGetBranchTarget(block, out ulong targetAddress))
{
@ -230,6 +231,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
CpuPreset cpuPreset,
IMemoryManager memoryManager,
ulong address,
ref int totalInsts,
ref RegisterMask useMask,
ref bool hasHostCall,
ref bool hasMemoryInstruction)
@ -255,7 +257,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
(name, flags, AddressForm addressForm) = InstTable.GetInstNameAndFlags(encoding, cpuPreset.Version, cpuPreset.Features);
if (name.IsPrivileged())
if (name.IsPrivileged() || (name == InstName.Sys && IsPrivilegedSys(encoding)))
{
name = InstName.UdfPermUndef;
flags = InstFlags.None;
@ -272,7 +274,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
uint tempGprUseMask = gprUseMask | instGprReadMask | instGprWriteMask;
if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(tempGprUseMask) || insts.Count >= MaxInstructionsPerBlock)
if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(tempGprUseMask) || totalInsts++ >= MaxInstructionsPerFunction)
{
isTruncated = true;
address -= 4UL;
@ -339,6 +341,11 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
return new(startAddress, address, insts, !isTruncated && !name.IsException(), isTruncated, isLoopEnd);
}
private static bool IsPrivilegedSys(uint encoding)
{
return !SysUtils.IsCacheInstEl0(encoding);
}
private static bool IsMrsNzcv(uint encoding)
{
return (encoding & ~0x1fu) == 0xd53b4200u;

View File

@ -13,6 +13,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
public static void RewriteSysInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, uint encoding)
{
// TODO: Handle IC instruction, it should invalidate the JIT cache.
if (InstEmitSystem.IsCacheInstForbidden(encoding))
{
// Current OS does not allow cache maintenance instructions from user mode, just do nothing.
return;
}
int rtIndex = RegisterUtils.ExtractRt(encoding);
if (rtIndex == RegisterUtils.ZrIndex)
{

View File

@ -69,7 +69,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
asm.LdrRiUn(Register((int)rd), Register(regAlloc.FixedContextRegister), NativeContextOffsets.TpidrEl0Offset);
}
}
else if ((encoding & ~0x1f) == 0xd53b0020 && IsAppleOS()) // mrs x0, ctr_el0
else if ((encoding & ~0x1f) == 0xd53b0020 && IsCtrEl0AccessForbidden()) // mrs x0, ctr_el0
{
uint rd = encoding & 0x1f;
@ -115,7 +115,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
{
return true;
}
else if ((encoding & ~0x1f) == 0xd53b0020 && IsAppleOS()) // mrs x0, ctr_el0
else if ((encoding & ~0x1f) == 0xd53b0020 && IsCtrEl0AccessForbidden()) // mrs x0, ctr_el0
{
return true;
}
@ -127,9 +127,16 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
return false;
}
private static bool IsAppleOS()
private static bool IsCtrEl0AccessForbidden()
{
return OperatingSystem.IsMacOS() || OperatingSystem.IsIOS();
// Only Linux allows accessing CTR_EL0 from user mode.
return OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS();
}
public static bool IsCacheInstForbidden(uint encoding)
{
// Windows does not allow the cache maintenance instructions to be used from user mode.
return OperatingSystem.IsWindows() && SysUtils.IsCacheInstUciTrapped(encoding);
}
public static bool NeedsContextStoreLoad(InstName name)

View File

@ -0,0 +1,389 @@
using Ryujinx.Memory;
using Ryujinx.Memory.Tracking;
using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Ryujinx.Cpu
{
/// <summary>
/// A page bitmap that keeps track of mapped state and tracking protection
/// for managed memory accesses (not using host page protection).
/// </summary>
internal readonly struct ManagedPageFlags
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
private readonly ulong[] _pageBitmap;
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
private enum ManagedPtBits : ulong
{
Unmapped = 0,
Mapped,
WriteTracked,
ReadWriteTracked,
MappedReplicated = 0x5555555555555555,
WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
ReadWriteTrackedReplicated = ulong.MaxValue,
}
public ManagedPageFlags(int addressSpaceBits)
{
int bits = Math.Max(0, addressSpaceBits - (PageBits + PageToPteShift));
_pageBitmap = new ulong[1 << bits];
}
/// <summary>
/// Computes the number of pages in a virtual address range.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <param name="startVa">The virtual address of the beginning of the first page</param>
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPagesCount(ulong va, ulong size, out ulong startVa)
{
// WARNING: Always check if ulong does not overflow during the operations.
startVa = va & ~(ulong)PageMask;
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
return (int)(vaSpan / PageSize);
}
/// <summary>
/// Checks if the page at a given CPU virtual address is mapped.
/// </summary>
/// <param name="va">Virtual address to check</param>
/// <returns>True if the address is mapped, false otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsMapped(ulong va)
{
ulong page = va >> PageBits;
int bit = (int)((page & 31) << 1);
int pageIndex = (int)(page >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
return ((pte >> bit) & 3) != 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
{
startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
pageIndex = (int)(pageStart >> PageToPteShift);
pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
}
/// <summary>
/// Checks if a memory range is mapped.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the entire range is mapped, false otherwise</returns>
public readonly bool IsRangeMapped(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
if (pages == 1)
{
return IsMapped(va);
}
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
// Check if either bit in each 2 bit page entry is set.
// OR the block with itself shifted down by 1, and check the first bit of each entry.
ulong mask = BlockMappedMask & startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
pte |= pte >> 1;
if ((pte & mask) != mask)
{
return false;
}
mask = BlockMappedMask;
}
return true;
}
/// <summary>
/// Reprotect a region of virtual memory for tracking.
/// </summary>
/// <param name="va">Virtual address base</param>
/// <param name="size">Size of the region to protect</param>
/// <param name="protection">Memory protection to set</param>
public readonly void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
{
// Protection is inverted on software pages, since the default value is 0.
protection = (~protection) & MemoryPermission.ReadAndWrite;
int pages = GetPagesCount(va, size, out va);
ulong pageStart = va >> PageBits;
if (pages == 1)
{
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)ManagedPtBits.Mapped,
MemoryPermission.Write => (ulong)ManagedPtBits.WriteTracked,
_ => (ulong)ManagedPtBits.ReadWriteTracked,
};
int bit = (int)((pageStart & 31) << 1);
ulong tagMask = 3UL << bit;
ulong invTagMask = ~tagMask;
ulong tag = protTag << bit;
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)ManagedPtBits.MappedReplicated,
MemoryPermission.Write => (ulong)ManagedPtBits.WriteTrackedReplicated,
_ => (ulong)ManagedPtBits.ReadWriteTrackedReplicated,
};
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Change the protection of all 2 bit entries that are mapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask &= mask; // Only update mapped pages within the given range.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
mask = ulong.MaxValue;
}
}
}
/// <summary>
/// Alerts the memory tracking that a given region has been read from or written to.
/// This should be called before read/write is performed.
/// </summary>
/// <param name="tracking">Memory tracking structure to call when pages are protected</param>
/// <param name="va">Virtual address of the region</param>
/// <param name="size">Size of the region</param>
/// <param name="write">True if the region was written, false if read</param>
/// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
/// <remarks>
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SignalMemoryTracking(MemoryTracking tracking, ulong va, ulong size, bool write, int? exemptId = null)
{
// Software table, used for managed memory tracking.
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
if (pages == 1)
{
ulong tag = (ulong)(write ? ManagedPtBits.WriteTracked : ManagedPtBits.ReadWriteTracked);
int bit = (int)((pageStart & 31) << 1);
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
ulong state = ((pte >> bit) & 3);
if (state >= tag)
{
tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
return;
}
else if (state == 0)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong anyTrackingTag = (ulong)ManagedPtBits.WriteTrackedReplicated;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
ulong mappedMask = mask & BlockMappedMask;
ulong mappedPte = pte | (pte >> 1);
if ((mappedPte & mappedMask) != mappedMask)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
pte &= mask;
if ((pte & anyTrackingTag) != 0) // Search for any tracking.
{
// Writes trigger any tracking.
// Only trigger tracking from reads if both bits are set on any page.
if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
{
tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
break;
}
}
mask = ulong.MaxValue;
}
}
}
/// <summary>
/// Adds the given address mapping to the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be mapped</param>
public readonly void AddMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Map all 2-bit entries that are unmapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
mask = ulong.MaxValue;
}
}
/// <summary>
/// Removes the given address mapping from the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be unmapped</param>
public readonly void RemoveMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
startMask = ~startMask;
endMask = ~endMask;
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask |= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
mask = 0;
}
}
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
}
}

View File

@ -1,10 +1,13 @@
using Ryujinx.Memory;
using System.Diagnostics;
using System.Numerics;
using System.Threading;
namespace Ryujinx.Cpu
{
public abstract class MemoryManagerBase : IRefCounted
public abstract class VirtualMemoryManagerRefCountedBase<TVirtual, TPhysical> : VirtualMemoryManagerBase<TVirtual, TPhysical>, IRefCounted
where TVirtual : IBinaryInteger<TVirtual>
where TPhysical : IBinaryInteger<TPhysical>
{
private int _referenceCount;

View File

@ -147,6 +147,8 @@ namespace Ryujinx.Graphics.GAL
A1B5G5R5Unorm,
B8G8R8A8Unorm,
B8G8R8A8Srgb,
B10G10R10A2Unorm,
X8UintD24Unorm,
}
public static class FormatExtensions
@ -260,6 +262,7 @@ namespace Ryujinx.Graphics.GAL
case Format.R10G10B10A2Sint:
case Format.R10G10B10A2Uscaled:
case Format.R10G10B10A2Sscaled:
case Format.B10G10R10A2Unorm:
return 4;
case Format.S8Uint:
@ -267,6 +270,7 @@ namespace Ryujinx.Graphics.GAL
case Format.D16Unorm:
return 2;
case Format.S8UintD24Unorm:
case Format.X8UintD24Unorm:
case Format.D32Float:
case Format.D24UnormS8Uint:
return 4;
@ -347,6 +351,7 @@ namespace Ryujinx.Graphics.GAL
case Format.D16Unorm:
case Format.D24UnormS8Uint:
case Format.S8UintD24Unorm:
case Format.X8UintD24Unorm:
case Format.D32Float:
case Format.D32FloatS8Uint:
return true;
@ -451,6 +456,7 @@ namespace Ryujinx.Graphics.GAL
case Format.R32G32Uint:
case Format.B8G8R8A8Unorm:
case Format.B8G8R8A8Srgb:
case Format.B10G10R10A2Unorm:
case Format.R10G10B10A2Unorm:
case Format.R10G10B10A2Uint:
case Format.R8G8B8A8Unorm:
@ -611,6 +617,7 @@ namespace Ryujinx.Graphics.GAL
case Format.B5G5R5A1Unorm:
case Format.B8G8R8A8Unorm:
case Format.B8G8R8A8Srgb:
case Format.B10G10R10A2Unorm:
return true;
}
@ -629,6 +636,7 @@ namespace Ryujinx.Graphics.GAL
case Format.D16Unorm:
case Format.D24UnormS8Uint:
case Format.S8UintD24Unorm:
case Format.X8UintD24Unorm:
case Format.D32Float:
case Format.D32FloatS8Uint:
case Format.S8Uint:

View File

@ -4,11 +4,13 @@ namespace Ryujinx.Graphics.GAL
{
public string GpuVendor { get; }
public string GpuModel { get; }
public string GpuDriver { get; }
public HardwareInfo(string gpuVendor, string gpuModel)
public HardwareInfo(string gpuVendor, string gpuModel, string gpuDriver)
{
GpuVendor = gpuVendor;
GpuModel = gpuModel;
GpuDriver = gpuDriver;
}
}
}

View File

@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.GAL
void SetIndexBuffer(BufferRange buffer, IndexType type);
void SetImage(int binding, ITexture texture, Format imageFormat);
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
void SetLineParameters(float width, bool smooth);

View File

@ -1,17 +1,20 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetImageCommand : IGALCommand, IGALCommand<SetImageCommand>
{
public readonly CommandType CommandType => CommandType.SetImage;
private ShaderStage _stage;
private int _binding;
private TableRef<ITexture> _texture;
private Format _imageFormat;
public void Set(int binding, TableRef<ITexture> texture, Format imageFormat)
public void Set(ShaderStage stage, int binding, TableRef<ITexture> texture, Format imageFormat)
{
_stage = stage;
_binding = binding;
_texture = texture;
_imageFormat = imageFormat;
@ -19,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetImage(command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
}
}
}

View File

@ -177,9 +177,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetImage(int binding, ITexture texture, Format imageFormat)
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
{
_renderer.New<SetImageCommand>().Set(binding, Ref(texture), imageFormat);
_renderer.New<SetImageCommand>().Set(stage, binding, Ref(texture), imageFormat);
_renderer.QueueCommand();
}

View File

@ -277,6 +277,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa + (ulong)srcBaseOffset, srcSize, true);
// If remapping is disabled, we always copy the components directly, in order.
// If it's enabled, but the mapping is just XYZW, we also copy them in order.
bool isIdentityRemap = !remap ||
(_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX &&
(dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) &&
(dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) &&
(dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW));
bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount);
bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount);
@ -284,7 +292,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
// but only if we are doing a complete copy,
// and not for block linear to linear copies, since those are typically accessed from the CPU.
if (completeSource && completeDest && !(dstLinear && !srcLinear))
if (completeSource && completeDest && !(dstLinear && !srcLinear) && isIdentityRemap)
{
var target = memoryManager.Physical.TextureCache.FindTexture(
memoryManager,
@ -353,14 +361,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
TextureParams srcParams = new(srcRegionX, srcRegionY, srcBaseOffset, srcBpp, srcLinear, srcCalculator);
TextureParams dstParams = new(dstRegionX, dstRegionY, dstBaseOffset, dstBpp, dstLinear, dstCalculator);
// If remapping is enabled, we always copy the components directly, in order.
// If it's enabled, but the mapping is just XYZW, we also copy them in order.
bool isIdentityRemap = !remap ||
(_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX &&
(dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) &&
(dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) &&
(dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW));
if (isIdentityRemap)
{
// The order of the components doesn't change, so we can just copy directly

View File

@ -26,6 +26,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
public const int PrimitiveRestartStateIndex = 12;
public const int RenderTargetStateIndex = 27;
// Vertex buffers larger than this size will be clamped to the mapped size.
private const ulong VertexBufferSizeToMappedSizeThreshold = 256 * 1024 * 1024; // 256 MB
private readonly GpuContext _context;
private readonly GpuChannel _channel;
private readonly DeviceStateWithShadow<ThreedClassState> _state;
@ -340,11 +343,22 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
bool unalignedChanged = _currentSpecState.SetHasUnalignedStorageBuffer(_channel.BufferManager.HasUnalignedStorageBuffers);
if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState) || unalignedChanged)
bool scaleMismatch;
do
{
// Shader must be reloaded. _vtgWritesRtLayer should not change.
UpdateShaderState();
if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState, out scaleMismatch) || unalignedChanged)
{
// Shader must be reloaded. _vtgWritesRtLayer should not change.
UpdateShaderState();
}
if (scaleMismatch)
{
// Binding textures changed scale of the bound render targets, correct the render target scale and rebind.
UpdateRenderTargetState();
}
}
while (scaleMismatch);
_channel.BufferManager.CommitGraphicsBindings(_drawState.DrawIndexed);
}
@ -1144,6 +1158,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
size = Math.Min(size, maxVertexBufferSize);
}
else if (size > VertexBufferSizeToMappedSizeThreshold)
{
// Make sure we have a sane vertex buffer size, since in some cases applications
// might set the "end address" of the vertex buffer to the end of the GPU address space,
// which would result in a several GBs large buffer.
size = _channel.MemoryManager.GetMappedSize(address, size);
}
}
else
{

View File

@ -37,6 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
R16G16Sint = 0xdc,
R16G16Uint = 0xdd,
R16G16Float = 0xde,
B10G10R10A2Unorm = 0xdf,
R11G11B10Float = 0xe0,
R32Sint = 0xe3,
R32Uint = 0xe4,
@ -104,6 +105,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
ColorFormat.R16G16Sint => new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2),
ColorFormat.R16G16Uint => new FormatInfo(Format.R16G16Uint, 1, 1, 4, 2),
ColorFormat.R16G16Float => new FormatInfo(Format.R16G16Float, 1, 1, 4, 2),
ColorFormat.B10G10R10A2Unorm => new FormatInfo(Format.B10G10R10A2Unorm, 1, 1, 4, 4),
ColorFormat.R11G11B10Float => new FormatInfo(Format.R11G11B10Float, 1, 1, 4, 3),
ColorFormat.R32Sint => new FormatInfo(Format.R32Sint, 1, 1, 4, 1),
ColorFormat.R32Uint => new FormatInfo(Format.R32Uint, 1, 1, 4, 1),

View File

@ -8,13 +8,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
/// </summary>
enum ZetaFormat
{
D32Float = 0xa,
D16Unorm = 0x13,
D24UnormS8Uint = 0x14,
D24Unorm = 0x15,
S8UintD24Unorm = 0x16,
Zf32 = 0xa,
Z16 = 0x13,
Z24S8 = 0x14,
X8Z24 = 0x15,
S8Z24 = 0x16,
S8Uint = 0x17,
D32FloatS8Uint = 0x19,
Zf32X24S8 = 0x19,
}
static class ZetaFormatConverter
@ -29,14 +29,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
return format switch
{
#pragma warning disable IDE0055 // Disable formatting
ZetaFormat.D32Float => new FormatInfo(Format.D32Float, 1, 1, 4, 1),
ZetaFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
ZetaFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
ZetaFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 1),
ZetaFormat.S8UintD24Unorm => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2),
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
ZetaFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
_ => FormatInfo.Default,
ZetaFormat.Zf32 => new FormatInfo(Format.D32Float, 1, 1, 4, 1),
ZetaFormat.Z16 => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
ZetaFormat.Z24S8 => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
ZetaFormat.X8Z24 => new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 1),
ZetaFormat.S8Z24 => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2),
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
ZetaFormat.Zf32X24S8 => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
_ => FormatInfo.Default,
#pragma warning restore IDE0055
};
}

View File

@ -46,7 +46,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
private const int MinCountForDeletion = 32;
private const int MaxCapacity = 2048;
private const ulong MaxTextureSizeCapacity = 512 * 1024 * 1024; // MB;
private const ulong MaxTextureSizeCapacity = 1024 * 1024 * 1024; // MB;
private readonly LinkedList<Texture> _textures;
private ulong _totalSize;

View File

@ -185,6 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Image
G24R8RUintGUnormBUnormAUnorm = G24R8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a0e
Z24S8RUintGUnormBUnormAUnorm = Z24S8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a29
Z24S8RUintGUnormBUintAUint = Z24S8 | RUint | GUnorm | BUint | AUint, // 0x48a29
X8Z24RUnormGUintBUintAUint = X8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912a
S8Z24RUnormGUintBUintAUint = S8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912b
R32B24G8RFloatGUintBUnormAUnorm = R32B24G8 | RFloat | GUint | BUnorm | AUnorm, // 0x25385
Zf32X24S8RFloatGUintBUnormAUnorm = Zf32X24S8 | RFloat | GUint | BUnorm | AUnorm, // 0x253b0
@ -410,6 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ TextureFormat.G24R8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ TextureFormat.Z24S8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ TextureFormat.Z24S8RUintGUnormBUintAUint, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
{ TextureFormat.X8Z24RUnormGUintBUintAUint, new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 2) },
{ TextureFormat.S8Z24RUnormGUintBUintAUint, new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2) },
{ TextureFormat.R32B24G8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
{ TextureFormat.Zf32X24S8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
@ -672,9 +674,9 @@ namespace Ryujinx.Graphics.Gpu.Image
{
1 => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1),
2 => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1),
4 => new FormatInfo(Format.R32Float, 1, 1, 4, 1),
8 => new FormatInfo(Format.R32G32Float, 1, 1, 8, 2),
16 => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4),
4 => new FormatInfo(Format.R32Uint, 1, 1, 4, 1),
8 => new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2),
16 => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4),
_ => format,
};
}

View File

@ -69,7 +69,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Address = address;
Size = size;
_memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool);
_memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool, RegionFlags.None);
_memoryTracking.RegisterPreciseAction(address, size, PreciseAction);
_modifiedDelegate = RegionModified;
}

View File

@ -634,7 +634,7 @@ namespace Ryujinx.Graphics.Gpu.Image
state.Texture = hostTextureRebind;
state.ImageFormat = format;
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind, format);
}
continue;
@ -692,7 +692,7 @@ namespace Ryujinx.Graphics.Gpu.Image
state.ImageFormat = format;
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture, format);
}
state.CachedTexture = texture;

View File

@ -242,7 +242,8 @@ namespace Ryujinx.Graphics.Gpu.Image
return TextureMatchQuality.FormatAlias;
}
else if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
lhs.FormatInfo.Format == Format.S8UintD24Unorm ||
lhs.FormatInfo.Format == Format.X8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
{
return TextureMatchQuality.FormatAlias;
}

View File

@ -1630,12 +1630,6 @@ namespace Ryujinx.Graphics.Gpu.Image
return;
}
// If size is zero, we have nothing to flush.
if (size == 0)
{
return;
}
// There is a small gap here where the action is removed but _actionRegistered is still 1.
// In this case it will skip registering the action, but here we are already handling it,
// so there shouldn't be any issue as it's the same handler for all actions.

View File

@ -360,15 +360,16 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Commits bindings on the graphics pipeline.
/// </summary>
/// <param name="specState">Specialization state for the bound shader</param>
/// <param name="scaleMismatch">True if there is a scale mismatch in the render targets, indicating they must be re-evaluated</param>
/// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
public bool CommitGraphicsBindings(ShaderSpecializationState specState)
public bool CommitGraphicsBindings(ShaderSpecializationState specState, out bool scaleMismatch)
{
_texturePoolCache.Tick();
_samplerPoolCache.Tick();
bool result = _gpBindingsManager.CommitBindings(specState);
UpdateRenderTargets();
scaleMismatch = UpdateRenderTargets();
return result;
}
@ -426,9 +427,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// Update host framebuffer attachments based on currently bound render target buffers.
/// </summary>
public void UpdateRenderTargets()
/// <returns>True if there is a scale mismatch in the render targets, indicating they must be re-evaluated</returns>
public bool UpdateRenderTargets()
{
bool anyChanged = false;
float expectedScale = RenderTargetScale;
bool scaleMismatch = false;
Texture dsTexture = _rtDepthStencil;
ITexture hostDsTexture = null;
@ -448,6 +452,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_rtHostDs = hostDsTexture;
anyChanged = true;
if (dsTexture != null && dsTexture.ScaleFactor != expectedScale)
{
scaleMismatch = true;
}
}
for (int index = 0; index < _rtColors.Length; index++)
@ -470,6 +479,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_rtHostColors[index] = hostTexture;
anyChanged = true;
if (texture != null && texture.ScaleFactor != expectedScale)
{
scaleMismatch = true;
}
}
}
@ -477,6 +491,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
}
return scaleMismatch;
}
/// <summary>

View File

@ -5,6 +5,8 @@ using Ryujinx.Memory.Tracking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory
{
@ -65,6 +67,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly Action<ulong, ulong> _loadDelegate;
private readonly Action<ulong, ulong> _modifiedDelegate;
private HashSet<MultiRangeBuffer> _virtualDependencies;
private readonly ReaderWriterLockSlim _virtualDependenciesLock;
private int _sequenceNumber;
private readonly bool _useGranular;
@ -123,13 +128,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (_useGranular)
{
_memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, baseHandles);
_memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, RegionFlags.UnalignedAccess, baseHandles);
_memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction);
}
else
{
_memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer);
_memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer, RegionFlags.UnalignedAccess);
if (baseHandles != null)
{
@ -152,6 +157,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
_externalFlushDelegate = new RegionSignal(ExternalFlush);
_loadDelegate = new Action<ulong, ulong>(LoadRegion);
_modifiedDelegate = new Action<ulong, ulong>(RegionModified);
_virtualDependenciesLock = new ReaderWriterLockSlim();
}
/// <summary>
@ -220,6 +227,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </remarks>
/// <param name="address">Start address of the range to synchronize</param>
/// <param name="size">Size in bytes of the range to synchronize</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SynchronizeMemory(ulong address, ulong size)
{
if (_useGranular)
@ -239,6 +247,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
else
{
_context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
CopyToDependantVirtualBuffers();
}
_sequenceNumber = _context.SequenceNumber;
@ -460,6 +469,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
int offset = (int)(mAddress - Address);
_context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
CopyToDependantVirtualBuffers(mAddress, mSize);
}
/// <summary>
@ -520,6 +531,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="dstOffset">The offset of the destination buffer to copy into</param>
public void CopyTo(Buffer destination, int dstOffset)
{
CopyFromDependantVirtualBuffers();
_context.Renderer.Pipeline.CopyBuffer(Handle, destination.Handle, 0, dstOffset, (int)Size);
}
@ -536,7 +548,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
_physicalMemory.WriteUntracked(address, data.Get());
_physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size));
}
/// <summary>
@ -617,6 +629,207 @@ namespace Ryujinx.Graphics.Gpu.Memory
UnmappedSequence++;
}
/// <summary>
/// Adds a virtual buffer dependency, indicating that a virtual buffer depends on data from this buffer.
/// </summary>
/// <param name="virtualBuffer">Dependant virtual buffer</param>
public void AddVirtualDependency(MultiRangeBuffer virtualBuffer)
{
_virtualDependenciesLock.EnterWriteLock();
try
{
(_virtualDependencies ??= new()).Add(virtualBuffer);
}
finally
{
_virtualDependenciesLock.ExitWriteLock();
}
}
/// <summary>
/// Removes a virtual buffer dependency, indicating that a virtual buffer no longer depends on data from this buffer.
/// </summary>
/// <param name="virtualBuffer">Dependant virtual buffer</param>
public void RemoveVirtualDependency(MultiRangeBuffer virtualBuffer)
{
_virtualDependenciesLock.EnterWriteLock();
try
{
if (_virtualDependencies != null)
{
_virtualDependencies.Remove(virtualBuffer);
if (_virtualDependencies.Count == 0)
{
_virtualDependencies = null;
}
}
}
finally
{
_virtualDependenciesLock.ExitWriteLock();
}
}
/// <summary>
/// Copies the buffer data to all virtual buffers that depends on it.
/// </summary>
public void CopyToDependantVirtualBuffers()
{
CopyToDependantVirtualBuffers(Address, Size);
}
/// <summary>
/// Copies the buffer data inside the specifide range to all virtual buffers that depends on it.
/// </summary>
/// <param name="address">Address of the range</param>
/// <param name="size">Size of the range in bytes</param>
public void CopyToDependantVirtualBuffers(ulong address, ulong size)
{
if (_virtualDependencies != null)
{
foreach (var virtualBuffer in _virtualDependencies)
{
CopyToDependantVirtualBuffer(virtualBuffer, address, size);
}
}
}
/// <summary>
/// Copies all modified ranges from all virtual buffers back into this buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CopyFromDependantVirtualBuffers()
{
if (_virtualDependencies != null)
{
CopyFromDependantVirtualBuffersImpl();
}
}
/// <summary>
/// Copies all modified ranges from all virtual buffers back into this buffer.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private void CopyFromDependantVirtualBuffersImpl()
{
foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber))
{
virtualBuffer.ConsumeModifiedRegion(this, (mAddress, mSize) =>
{
// Get offset inside both this and the virtual buffer.
// Note that sometimes there is no right answer for the virtual offset,
// as the same physical range might be mapped multiple times inside a virtual buffer.
// We just assume it does not happen in practice as it can only be implemented correctly
// when the host has support for proper sparse mapping.
ulong mEndAddress = mAddress + mSize;
mAddress = Math.Max(mAddress, Address);
mSize = Math.Min(mEndAddress, EndAddress) - mAddress;
int physicalOffset = (int)(mAddress - Address);
int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize));
_context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)mSize);
});
}
}
/// <summary>
/// Copies all overlapping modified ranges from all virtual buffers back into this buffer, and returns an updated span with the data.
/// </summary>
/// <param name="dataSpan">Span where the unmodified data will be taken from for the output</param>
/// <param name="address">Address of the region to copy</param>
/// <param name="size">Size of the region to copy in bytes</param>
/// <returns>A span with <paramref name="dataSpan"/>, and the data for all modified ranges if any</returns>
private ReadOnlySpan<byte> CopyFromDependantVirtualBuffers(ReadOnlySpan<byte> dataSpan, ulong address, ulong size)
{
_virtualDependenciesLock.EnterReadLock();
try
{
if (_virtualDependencies != null)
{
byte[] storage = dataSpan.ToArray();
foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber))
{
virtualBuffer.ConsumeModifiedRegion(address, size, (mAddress, mSize) =>
{
// Get offset inside both this and the virtual buffer.
// Note that sometimes there is no right answer for the virtual offset,
// as the same physical range might be mapped multiple times inside a virtual buffer.
// We just assume it does not happen in practice as it can only be implemented correctly
// when the host has support for proper sparse mapping.
ulong mEndAddress = mAddress + mSize;
mAddress = Math.Max(mAddress, address);
mSize = Math.Min(mEndAddress, address + size) - mAddress;
int physicalOffset = (int)(mAddress - Address);
int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize));
_context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)size);
virtualBuffer.GetData(storage.AsSpan().Slice((int)(mAddress - address), (int)mSize), virtualOffset, (int)mSize);
});
}
dataSpan = storage;
}
}
finally
{
_virtualDependenciesLock.ExitReadLock();
}
return dataSpan;
}
/// <summary>
/// Copies the buffer data to the specified virtual buffer.
/// </summary>
/// <param name="virtualBuffer">Virtual buffer to copy the data into</param>
public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer)
{
CopyToDependantVirtualBuffer(virtualBuffer, Address, Size);
}
/// <summary>
/// Copies the buffer data inside the given range to the specified virtual buffer.
/// </summary>
/// <param name="virtualBuffer">Virtual buffer to copy the data into</param>
/// <param name="address">Address of the range</param>
/// <param name="size">Size of the range in bytes</param>
public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer, ulong address, ulong size)
{
// Broadcast data to all ranges of the virtual buffer that are contained inside this buffer.
ulong lastOffset = 0;
while (virtualBuffer.TryGetPhysicalOffset(this, lastOffset, out ulong srcOffset, out ulong dstOffset, out ulong copySize))
{
ulong innerOffset = address - Address;
ulong innerEndOffset = (address + size) - Address;
lastOffset = dstOffset + copySize;
// Clamp range to the specified range.
ulong copySrcOffset = Math.Max(srcOffset, innerOffset);
ulong copySrcEndOffset = Math.Min(innerEndOffset, srcOffset + copySize);
if (copySrcEndOffset > copySrcOffset)
{
copySize = copySrcEndOffset - copySrcOffset;
dstOffset += copySrcOffset - srcOffset;
srcOffset = copySrcOffset;
_context.Renderer.Pipeline.CopyBuffer(Handle, virtualBuffer.Handle, (int)srcOffset, (int)dstOffset, (int)copySize);
}
}
}
/// <summary>
/// Increments the buffer reference count.
/// </summary>

View File

@ -3,6 +3,7 @@ using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Gpu.Memory
{
@ -46,6 +47,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly Dictionary<ulong, BufferCacheEntry> _dirtyCache;
private readonly Dictionary<ulong, BufferCacheEntry> _modifiedCache;
private bool _pruneCaches;
private int _virtualModifiedSequenceNumber;
public event Action NotifyBuffersModified;
@ -125,7 +127,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Performs address translation of the GPU virtual address, and creates
/// new buffers, if needed, for the specified range.
/// new physical and virtual buffers, if needed, for the specified range.
/// </summary>
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
@ -138,12 +140,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
return new MultiRange(MemoryManager.PteUnmapped, size);
}
bool supportsSparse = _context.Capabilities.SupportsSparseBuffer;
// Fast path not taken for non-contiguous ranges,
// since multi-range buffers are not coalesced, so a buffer that covers
// the entire cached range might not actually exist.
if (memoryManager.VirtualBufferCache.TryGetOrAddRange(gpuVa, size, supportsSparse, out MultiRange range) &&
if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) &&
range.Count == 1)
{
return range;
@ -154,6 +154,50 @@ namespace Ryujinx.Graphics.Gpu.Memory
return range;
}
/// <summary>
/// Performs address translation of the GPU virtual address, and creates
/// new physical buffers, if needed, for the specified range.
/// </summary>
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
/// <param name="size">Size in bytes of the buffer</param>
/// <returns>Physical ranges of the buffer, after address translation</returns>
public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size)
{
if (gpuVa == 0)
{
return new MultiRange(MemoryManager.PteUnmapped, size);
}
// Fast path not taken for non-contiguous ranges,
// since multi-range buffers are not coalesced, so a buffer that covers
// the entire cached range might not actually exist.
if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) &&
range.Count == 1)
{
return range;
}
for (int i = 0; i < range.Count; i++)
{
MemoryRange subRange = range.GetSubRange(i);
if (subRange.Address != MemoryManager.PteUnmapped)
{
if (range.Count > 1)
{
CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize);
}
else
{
CreateBuffer(subRange.Address, subRange.Size);
}
}
}
return range;
}
/// <summary>
/// Creates a new buffer for the specified range, if it does not yet exist.
/// This can be used to ensure the existance of a buffer.
@ -263,41 +307,108 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
BufferRange[] storages = new BufferRange[range.Count];
MultiRangeBuffer multiRangeBuffer;
MemoryRange[] alignedSubRanges = new MemoryRange[range.Count];
ulong alignmentMask = SparseBufferAlignmentSize - 1;
for (int i = 0; i < range.Count; i++)
if (_context.Capabilities.SupportsSparseBuffer)
{
MemoryRange subRange = range.GetSubRange(i);
BufferRange[] storages = new BufferRange[range.Count];
for (int i = 0; i < range.Count; i++)
{
MemoryRange subRange = range.GetSubRange(i);
if (subRange.Address != MemoryManager.PteUnmapped)
{
ulong endAddress = subRange.Address + subRange.Size;
ulong alignedAddress = subRange.Address & ~alignmentMask;
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
ulong alignedSize = alignedEndAddress - alignedAddress;
Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize);
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
storages[i] = bufferRange;
}
else
{
ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask;
alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize);
storages[i] = new BufferRange(BufferHandle.Null, 0, (int)alignedSize);
}
}
multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges), storages);
}
else
{
for (int i = 0; i < range.Count; i++)
{
MemoryRange subRange = range.GetSubRange(i);
if (subRange.Address != MemoryManager.PteUnmapped)
{
ulong endAddress = subRange.Address + subRange.Size;
ulong alignedAddress = subRange.Address & ~alignmentMask;
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
ulong alignedSize = alignedEndAddress - alignedAddress;
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
}
else
{
ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask;
alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize);
}
}
multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges));
UpdateVirtualBufferDependencies(multiRangeBuffer);
}
_multiRangeBuffers.Add(multiRangeBuffer);
}
/// <summary>
/// Adds two-way dependencies to all physical buffers contained within a given virtual buffer.
/// </summary>
/// <param name="virtualBuffer">Virtual buffer to have dependencies added</param>
private void UpdateVirtualBufferDependencies(MultiRangeBuffer virtualBuffer)
{
virtualBuffer.ClearPhysicalDependencies();
ulong dstOffset = 0;
HashSet<Buffer> physicalBuffers = new();
for (int i = 0; i < virtualBuffer.Range.Count; i++)
{
MemoryRange subRange = virtualBuffer.Range.GetSubRange(i);
if (subRange.Address != MemoryManager.PteUnmapped)
{
ulong endAddress = subRange.Address + subRange.Size;
Buffer buffer = _buffers.FindFirstOverlap(subRange.Address, subRange.Size);
ulong alignedAddress = subRange.Address & ~alignmentMask;
ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
ulong alignedSize = alignedEndAddress - alignedAddress;
Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize);
BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
storages[i] = bufferRange;
alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size);
physicalBuffers.Add(buffer);
}
else
{
ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask;
storages[i] = new BufferRange(BufferHandle.Null, 0, (int)alignedSize);
alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize);
}
dstOffset += subRange.Size;
}
MultiRangeBuffer multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges), storages);
_multiRangeBuffers.Add(multiRangeBuffer);
foreach (var buffer in physicalBuffers)
{
buffer.CopyToDependantVirtualBuffer(virtualBuffer);
}
}
/// <summary>
@ -620,8 +731,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the copy</param>
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
{
MultiRange srcRange = TranslateAndCreateMultiBuffers(memoryManager, srcVa, size);
MultiRange dstRange = TranslateAndCreateMultiBuffers(memoryManager, dstVa, size);
MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size);
MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size);
if (srcRange.Count == 1 && dstRange.Count == 1)
{
@ -701,6 +812,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
dstBuffer.ClearModified(dstAddress, size);
memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
}
dstBuffer.CopyToDependantVirtualBuffers(dstAddress, size);
}
/// <summary>
@ -715,7 +828,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="value">Value to be written into the buffer</param>
public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value)
{
MultiRange range = TranslateAndCreateMultiBuffers(memoryManager, gpuVa, size);
MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size);
for (int index = 0; index < range.Count; index++)
{
@ -727,6 +840,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
_context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value);
memoryManager.Physical.FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
buffer.CopyToDependantVirtualBuffers(subRange.Address, subRange.Size);
}
}
@ -806,6 +921,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
if (write && buffer != null && !_context.Capabilities.SupportsSparseBuffer)
{
buffer.AddModifiedRegion(range, ++_virtualModifiedSequenceNumber);
}
return buffer;
}
@ -825,6 +945,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
buffer = _buffers.FindFirstOverlap(address, size);
buffer.CopyFromDependantVirtualBuffers();
buffer.SynchronizeMemory(address, size);
if (write)
@ -849,14 +970,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (range.Count == 1)
{
MemoryRange subRange = range.GetSubRange(0);
SynchronizeBufferRange(subRange.Address, subRange.Size);
SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: true);
}
else
{
for (int index = 0; index < range.Count; index++)
{
MemoryRange subRange = range.GetSubRange(index);
SynchronizeBufferRange(subRange.Address, subRange.Size);
SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: false);
}
}
}
@ -866,12 +987,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
/// <param name="address">Start address of the memory range</param>
/// <param name="size">Size in bytes of the memory range</param>
private void SynchronizeBufferRange(ulong address, ulong size)
/// <param name="copyBackVirtual">Whether virtual buffers that uses this buffer as backing memory should have its data copied back if modified</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SynchronizeBufferRange(ulong address, ulong size, bool copyBackVirtual)
{
if (size != 0)
{
Buffer buffer = _buffers.FindFirstOverlap(address, size);
if (copyBackVirtual)
{
buffer.CopyFromDependantVirtualBuffers();
}
buffer.SynchronizeMemory(address, size);
}
}

View File

@ -484,7 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (binding.IsImage)
{
_context.Renderer.Pipeline.SetImage(binding.BindingInfo.Binding, binding.Texture, binding.Format);
_context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture, binding.Format);
}
else
{

View File

@ -40,9 +40,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
internal PhysicalMemory Physical { get; }
/// <summary>
/// Virtual buffer cache.
/// Virtual range cache.
/// </summary>
internal VirtualBufferCache VirtualBufferCache { get; }
internal VirtualRangeCache VirtualRangeCache { get; }
/// <summary>
/// Cache of GPU counters.
@ -56,12 +56,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
internal MemoryManager(PhysicalMemory physicalMemory)
{
Physical = physicalMemory;
VirtualBufferCache = new VirtualBufferCache(this);
VirtualRangeCache = new VirtualRangeCache(this);
CounterCache = new CounterCache();
_pageTable = new ulong[PtLvl0Size][];
MemoryUnmapped += Physical.TextureCache.MemoryUnmappedHandler;
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
MemoryUnmapped += VirtualBufferCache.MemoryUnmappedHandler;
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
}

View File

@ -1,6 +1,7 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Memory
{
@ -21,12 +22,73 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
public MultiRange Range { get; }
/// <summary>
/// Ever increasing counter value indicating when the buffer was modified relative to other buffers.
/// </summary>
public int ModificationSequenceNumber { get; private set; }
/// <summary>
/// Physical buffer dependency entry.
/// </summary>
private readonly struct PhysicalDependency
{
/// <summary>
/// Physical buffer.
/// </summary>
public readonly Buffer PhysicalBuffer;
/// <summary>
/// Offset of the range on the physical buffer.
/// </summary>
public readonly ulong PhysicalOffset;
/// <summary>
/// Offset of the range on the virtual buffer.
/// </summary>
public readonly ulong VirtualOffset;
/// <summary>
/// Size of the range.
/// </summary>
public readonly ulong Size;
/// <summary>
/// Creates a new physical dependency.
/// </summary>
/// <param name="physicalBuffer">Physical buffer</param>
/// <param name="physicalOffset">Offset of the range on the physical buffer</param>
/// <param name="virtualOffset">Offset of the range on the virtual buffer</param>
/// <param name="size">Size of the range</param>
public PhysicalDependency(Buffer physicalBuffer, ulong physicalOffset, ulong virtualOffset, ulong size)
{
PhysicalBuffer = physicalBuffer;
PhysicalOffset = physicalOffset;
VirtualOffset = virtualOffset;
Size = size;
}
}
private List<PhysicalDependency> _dependencies;
private BufferModifiedRangeList _modifiedRanges = null;
/// <summary>
/// Creates a new instance of the buffer.
/// </summary>
/// <param name="context">GPU context that the buffer belongs to</param>
/// <param name="range">Range of memory where the data is mapped</param>
/// <param name="storages">Backing memory for the buffers</param>
public MultiRangeBuffer(GpuContext context, MultiRange range)
{
_context = context;
Range = range;
Handle = context.Renderer.CreateBuffer((int)range.GetSize());
}
/// <summary>
/// Creates a new instance of the buffer.
/// </summary>
/// <param name="context">GPU context that the buffer belongs to</param>
/// <param name="range">Range of memory where the data is mapped</param>
/// <param name="storages">Backing memory for the buffer</param>
public MultiRangeBuffer(GpuContext context, MultiRange range, ReadOnlySpan<BufferRange> storages)
{
_context = context;
@ -49,11 +111,134 @@ namespace Ryujinx.Graphics.Gpu.Memory
return new BufferRange(Handle, offset, (int)range.GetSize());
}
/// <summary>
/// Removes all physical buffer dependencies.
/// </summary>
public void ClearPhysicalDependencies()
{
_dependencies?.Clear();
}
/// <summary>
/// Adds a physical buffer dependency.
/// </summary>
/// <param name="buffer">Physical buffer to be added</param>
/// <param name="rangeAddress">Address inside the physical buffer where the virtual buffer range is located</param>
/// <param name="dstOffset">Offset inside the virtual buffer where the physical range is located</param>
/// <param name="rangeSize">Size of the range in bytes</param>
public void AddPhysicalDependency(Buffer buffer, ulong rangeAddress, ulong dstOffset, ulong rangeSize)
{
(_dependencies ??= new()).Add(new(buffer, rangeAddress - buffer.Address, dstOffset, rangeSize));
buffer.AddVirtualDependency(this);
}
/// <summary>
/// Tries to get the physical range corresponding to the given physical buffer.
/// </summary>
/// <param name="buffer">Physical buffer</param>
/// <param name="minimumVirtOffset">Minimum virtual offset that a range match can have</param>
/// <param name="physicalOffset">Physical offset of the match</param>
/// <param name="virtualOffset">Virtual offset of the match, always greater than or equal <paramref name="minimumVirtOffset"/></param>
/// <param name="size">Size of the range match</param>
/// <returns>True if a match was found for the given parameters, false otherwise</returns>
public bool TryGetPhysicalOffset(Buffer buffer, ulong minimumVirtOffset, out ulong physicalOffset, out ulong virtualOffset, out ulong size)
{
physicalOffset = 0;
virtualOffset = 0;
size = 0;
if (_dependencies != null)
{
foreach (var dependency in _dependencies)
{
if (dependency.PhysicalBuffer == buffer && dependency.VirtualOffset >= minimumVirtOffset)
{
physicalOffset = dependency.PhysicalOffset;
virtualOffset = dependency.VirtualOffset;
size = dependency.Size;
return true;
}
}
}
return false;
}
/// <summary>
/// Adds a modified virtual memory range.
/// </summary>
/// <remarks>
/// This is only required when the host does not support sparse buffers, otherwise only physical buffers need to track modification.
/// </remarks>
/// <param name="range">Modified range</param>
/// <param name="modifiedSequenceNumber">ModificationSequenceNumber</param>
public void AddModifiedRegion(MultiRange range, int modifiedSequenceNumber)
{
_modifiedRanges ??= new(_context, null, null);
for (int i = 0; i < range.Count; i++)
{
MemoryRange subRange = range.GetSubRange(i);
_modifiedRanges.SignalModified(subRange.Address, subRange.Size);
}
ModificationSequenceNumber = modifiedSequenceNumber;
}
/// <summary>
/// Calls the specified <paramref name="rangeAction"/> for all modified ranges that overlaps with <paramref name="buffer"/>.
/// </summary>
/// <param name="buffer">Buffer to have its range checked</param>
/// <param name="rangeAction">Action to perform for modified ranges</param>
public void ConsumeModifiedRegion(Buffer buffer, Action<ulong, ulong> rangeAction)
{
ConsumeModifiedRegion(buffer.Address, buffer.Size, rangeAction);
}
/// <summary>
/// Calls the specified <paramref name="rangeAction"/> for all modified ranges that overlaps with <paramref name="address"/> and <paramref name="size"/>.
/// </summary>
/// <param name="address">Address of the region to consume</param>
/// <param name="size">Size of the region to consume</param>
/// <param name="rangeAction">Action to perform for modified ranges</param>
public void ConsumeModifiedRegion(ulong address, ulong size, Action<ulong, ulong> rangeAction)
{
if (_modifiedRanges != null)
{
_modifiedRanges.GetRanges(address, size, rangeAction);
_modifiedRanges.Clear(address, size);
}
}
/// <summary>
/// Gets data from the specified region of the buffer, and places it on <paramref name="output"/>.
/// </summary>
/// <param name="output">Span to put the data into</param>
/// <param name="offset">Offset of the buffer to get the data from</param>
/// <param name="size">Size of the data in bytes</param>
public void GetData(Span<byte> output, int offset, int size)
{
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, size);
data.Get().CopyTo(output);
}
/// <summary>
/// Disposes the host buffer.
/// </summary>
public void Dispose()
{
if (_dependencies != null)
{
foreach (var dependency in _dependencies)
{
dependency.PhysicalBuffer.RemoveVirtualDependency(this);
}
_dependencies = null;
}
_context.Renderer.DeleteBuffer(Handle);
}
}

View File

@ -368,10 +368,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="address">CPU virtual address of the region</param>
/// <param name="size">Size of the region</param>
/// <param name="kind">Kind of the resource being tracked</param>
/// <param name="flags">Region flags</param>
/// <returns>The memory tracking handle</returns>
public RegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind)
public RegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind, RegionFlags flags = RegionFlags.None)
{
return _cpuMemory.BeginTracking(address, size, (int)kind);
return _cpuMemory.BeginTracking(address, size, (int)kind, flags);
}
/// <summary>
@ -408,12 +409,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="address">CPU virtual address of the region</param>
/// <param name="size">Size of the region</param>
/// <param name="kind">Kind of the resource being tracked</param>
/// <param name="flags">Region flags</param>
/// <param name="handles">Handles to inherit state from or reuse</param>
/// <param name="granularity">Desired granularity of write tracking</param>
/// <returns>The memory tracking handle</returns>
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, ResourceKind kind, IEnumerable<IRegionHandle> handles = null, ulong granularity = 4096)
public MultiRegionHandle BeginGranularTracking(
ulong address,
ulong size,
ResourceKind kind,
RegionFlags flags = RegionFlags.None,
IEnumerable<IRegionHandle> handles = null,
ulong granularity = 4096)
{
return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind);
return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind, flags);
}
/// <summary>

View File

@ -6,9 +6,9 @@ using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
/// Virtual buffer cache.
/// Virtual range cache.
/// </summary>
class VirtualBufferCache
class VirtualRangeCache
{
private readonly MemoryManager _memoryManager;
@ -68,10 +68,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
private int _hasDeferredUnmaps;
/// <summary>
/// Creates a new instance of the virtual buffer cache.
/// Creates a new instance of the virtual range cache.
/// </summary>
/// <param name="memoryManager">Memory manager that the virtual buffer cache belongs to</param>
public VirtualBufferCache(MemoryManager memoryManager)
/// <param name="memoryManager">Memory manager that the virtual range cache belongs to</param>
public VirtualRangeCache(MemoryManager memoryManager)
{
_memoryManager = memoryManager;
_virtualRanges = new RangeList<VirtualRange>();
@ -102,10 +102,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary>
/// <param name="gpuVa">GPU virtual address to get the physical range from</param>
/// <param name="size">Size in bytes of the region</param>
/// <param name="supportsSparse">Indicates host support for sparse buffer mapping of non-contiguous ranges</param>
/// <param name="range">Physical range for the specified GPU virtual region</param>
/// <returns>True if the range already existed, false if a new one was created and added</returns>
public bool TryGetOrAddRange(ulong gpuVa, ulong size, bool supportsSparse, out MultiRange range)
public bool TryGetOrAddRange(ulong gpuVa, ulong size, out MultiRange range)
{
VirtualRange[] overlaps = _virtualRangeOverlaps;
int overlapsCount;
@ -158,7 +157,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
found = true;
found = overlap0.Range.Count == 1 || IsSparseAligned(overlap0.Range);
range = overlap0.Range.Slice(gpuVa - overlap0.Address, size);
}
}
@ -174,12 +173,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
ShrinkOverlapsBufferIfNeeded();
// If the the range is not properly aligned for sparse mapping,
// or if the host does not support sparse mapping, let's just
// force it to a single range.
// If the range is not properly aligned for sparse mapping,
// let's just force it to a single range.
// This might cause issues in some applications that uses sparse
// mappings.
if (!IsSparseAligned(range) || !supportsSparse)
if (!IsSparseAligned(range))
{
range = new MultiRange(range.GetSubRange(0).Address, size);
}

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 = 5958;
private const uint CodeGenVersion = 6462;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@ -30,6 +30,8 @@ namespace Ryujinx.Graphics.OpenGL
_thread.Start();
}
public bool HasContext() => _backgroundContext.HasContext();
private void Run()
{
InBackground = true;

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