Compare commits

...

56 Commits

Author SHA1 Message Date
f5a6f45b27 vulkan: Separate debug utils logic from VulkanInitialization (#4609)
* vulkan: Separate debug utils logic from VulkanInitialization

Also checks for VK_EXT_debug_utils existence instead of force enabling it and allow possible error during messenger init

* Address gdkchan's comment

* Use CreateDebugUtilsMessenger Span variant
2023-04-01 08:05:02 +00:00
210557951b nuget: bump Avalonia dependencies from 0.10.18 to 0.10.19 (#4602)
* infra: Update Avalonia to 0.10.19

* infra: Update XamlNameReferenceGenerator to 1.6.1
2023-04-01 07:27:34 +00:00
4c2d9ff3ff HLE: Refactoring of ApplicationLoader (#4480)
* HLE: Refactoring of ApplicationLoader

* Fix SDL2 Headless

* Addresses gdkchan feedback

* Fixes LoadUnpackedNca RomFS loading

* remove useless casting

* Cleanup and fixe empty application name

* Remove ProcessInfo

* Fixes typo

* ActiveProcess to ActiveApplication

* Update check

* Clean using.

* Use the correct filepath when loading Homebrew.npdm

* Fix NRE in ProcessResult if MetaLoader is null

* Add more checks for valid processId & return success

* Add missing logging statement for npdm error

* Return result for LoadKip()

* Move error logging out of PFS load extension method

This avoids logging "Could not find Main NCA"
followed by "Loading main..." when trying to start hbl.

* Fix GUIs not checking load results

* Fix style and formatting issues

* Fix formatting and wording

* gtk: Refactor LoadApplication()

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2023-03-31 21:16:46 +02:00
8198b99935 Fix Linux hang on shutdown (#4617)
* Rework StdErr-to-log redirection to use built-in FileStream, and do reads asynchronously to avoid hanging the process shutdown.

* set _disposable to false ASAP
2023-03-30 22:07:07 +02:00
460f96967d Slight Code Refactoring (#4373)
* Simplify return statements by using ternary expressions

* Remove a redundant type conversion

* Reduce nesting by inverting "if" statements

* Try to improve code readability by using LINQ and inverting "if" statements

* Try to improve code readability by using LINQ, using ternary expressions, and inverting "if" statements

* Add line breaks to long LINQ

* Add line breaks to long LINQ
2023-03-28 14:59:43 +02:00
7ca779a26d audout: Fix a possible crash with SDL2 when the SDL2 audio backend is dummy (#4605)
This change makes audio device error not fatal.
In case of error, the SDL2 audio backend will behave like the dummy
backend.
2023-03-27 20:56:36 +02:00
b5032b3c91 vulkan: Fix access level of extensions fields and make them readonly (#4608) 2023-03-27 08:40:27 +02:00
f0a3dff136 vulkan: Remove CreateCommandBufferPool from VulkanInitialization (#4606)
It was only called in one place, that can be simplified.
2023-03-27 02:16:31 +00:00
f659dcb9d8 vulkan: fix broken "VK_EXT_subgroup_size_control" support check (#4607)
Not sure since when it was broken...
2023-03-26 19:01:30 +02:00
a34fb0e939 Vulkan: Insert barriers before clears (#4596)
* Vulkan: Insert barriers before clears

Newer NVIDIA GPUs seem to be able to start clearing render targets before the last rasterization task is completed, which can cause it to clear a texture while it is being sampled.

This change adds a barrier from read to write when doing a clear, assuming it has been sampled in the past. It could be possible for this to be needed for sample into draw by some GPU, but it's not right now afaik.

This should fix visual artifacts on newer NVIDIA GPUs and driver combos. Contrary to popular belief, Tetris® Effect: Connected is not affected. Testing welcome, hopefully should fix most cases of this and not cost too much performance.

* Visual Studio Moment

* Address feedback

* Address Feedback 2
2023-03-26 12:51:02 +02:00
21ce8a9b80 chore: Update Ryujinx.SDL2-CS to 2.26.3 (#4479) 2023-03-24 22:42:24 +01:00
9ecbee8032 Batch inline index buffer update (#4587) 2023-03-24 14:19:54 +01:00
80519af67d Update short cache textures if modified (#4586) 2023-03-24 12:54:58 +01:00
26e30faff3 Fix handle leak on IShopServiceAccessServerInterface.CreateServerInterface (#4591) 2023-03-24 11:56:54 +01:00
0992310b76 ARMeilleure: Check for XSAVE cpuid flag for AVX{2,512} (#4584)
Protection for the `xgetbv` instruction for systems that do not support
`xcr0` such as nehalem processors.

The `XSAVE` cpuid indicates support for `XSAVE`, `XRESTOR`, `XSETBV`,
`XGETBV` while `OSXSAVE` indicates if the operating system itself has
`XSAVE` turned on. Both must be checked at the same time.
2023-03-22 14:51:21 -03:00
009c1101d2 CI: add a version tag to correlate release versions with commits (#4572)
* add step to tag commit with release version

* add step to tag commit with release version

* Rename step to “Create Tag”

* Fix name
2023-03-22 13:17:28 +01:00
ba95ee54ab Revert "Use source generated json serializers in order to improve code trimming (#4094)" (#4576)
This reverts commit 4ce4299ca2.
2023-03-21 20:14:46 -03:00
4ce4299ca2 Use source generated json serializers in order to improve code trimming (#4094)
* Use source generated json serializers in order to improve code trimming

* Use strongly typed github releases model to fetch updates instead of raw Newtonsoft.Json parsing

* Use separate model for LogEventArgs serialization

* Make dynamic object formatter static. Fix string builder pooling.

* Do not inherit json version of LogEventArgs from EventArgs

* Fix extra space in object formatting

* Write log json directly to stream instead of using buffer writer

* Rebase fixes

* Rebase fixes

* Rebase fixes

* Enforce block-scoped namespaces in the solution. Convert style for existing code

* Apply suggestions from code review

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Rebase indent fix

* Fix indent

* Delete unnecessary json properties

* Rebase fix

* Remove overridden json property names as they are handled in the options

* Apply suggestions from code review

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Use default json options in github api calls

* Indentation and spacing fixes

---------

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-03-21 19:41:19 -03:00
17620d18db ARMeilleure: Add initial support for AVX512 (EVEX encoding) (cont) (#4147)
* ARMeilleure: Add AVX512{F,VL,DQ,BW} detection

Add `UseAvx512Ortho` and `UseAvx512OrthoFloat` optimization flags as
short-hands for `F+VL` and `F+VL+DQ`.

* ARMeilleure: Add initial support for EVEX instruction encoding

Does not implement rounding, or exception controls.

* ARMeilleure: Add `X86Vpternlogd`

Accelerates the vector-`Not` instruction.

* ARMeilleure: Add check for `OSXSAVE` for AVX{2,512}

* ARMeilleure: Add check for `XCR0` flags

Add XCR0 register checks for AVX and AVX512F, following the guidelines
from section 14.3 and 15.2 from the Intel Architecture Software
Developer's Manual.

* ARMeilleure: Remove redundant `ReProtect` and `Dispose`, formatting

* ARMeilleure: Move XCR0 procedure to GetXcr0Eax

* ARMeilleure: Add `XCR0` to `FeatureInfo` structure

* ARMeilleure: Utilize `ReadOnlySpan` for Xcr0 assembly

Avoids an additional allocation

* ARMeilleure: Formatting fixes

* ARMeilleure: Fix EVEX encoding src2 register index

> Just like in VEX prefix, vvvv is provided in inverted form.

* ARMeilleure: Add `X86Vpternlogd` acceleration to `Vmvn_I`

Passes unit tests, verified instruction utilization

* ARMeilleure: Fix EVEX register operand designations

Operand 2 was being sourced improperly.

EVEX encoded instructions source their operands like so:
Operand 1: ModRM:reg
Operand 2: EVEX.vvvvv
Operand 3: ModRM:r/m
Operand 4: Imm

This fixes the improper register designations when emitting vpternlog.
Now "dest", "src1", "src2" arguments emit in the proper order in EVEX instructions.

* ARMeilleure: Add `X86Vpternlogd` acceleration to `Orn_V`

* ARMeilleure: PTC version bump

* ARMeilleure: Update EVEX encoding Debug.Assert to Debug.Fail

* ARMeilleure: Update EVEX encoding comment capitalization
2023-03-20 16:09:24 -03:00
9f1cf6458c Vulkan: Migrate buffers between memory types to improve GPU performance (#4540)
* Initial implementation of migration between memory heaps

- Missing OOM handling
- Missing `_map` data safety when remapping
  - Copy may not have completed yet (needs some kind of fence)
  - Map may be unmapped before it is done being used. (needs scoped access)
- SSBO accesses are all "writes" - maybe pass info in another way.
- Missing keeping map type when resizing buffers (should this be done?)

* Ensure migrated data is in place before flushing.

* Fix issue where old waitable would be signalled.

- There is a real issue where existing Auto<> references need to be replaced.

* Swap bound Auto<> instances when swapping buffer backing

* Fix conversion buffers

* Don't try move buffers if the host has shared memory.

* Make GPU methods return PinnedSpan with scope

* Storage Hint

* Fix stupidity

* Fix rebase

* Tweak rules

Attempt to sidestep BOTW slowdown

* Remove line

* Migrate only when command buffers flush

* Change backing swap log to debug

* Address some feedback

* Disallow backing swap when the flush lock is held by the current thread

* Make PinnedSpan from ReadOnlySpan explicitly unsafe

* Fix some small issues

- Index buffer swap fixed
- Allocate DeviceLocal buffers using a separate block list to images.

* Remove alternative flags

* Address feedback
2023-03-19 17:56:48 -03:00
67b4e63cff Remove MultiRange Min/MaxAddress and rename GetSlice to Slice (#4566)
* Delete MinAddress and MaxAddress from MultiRange

* Rename MultiRange.GetSlice to MultiRange.Slice
2023-03-19 17:31:35 +01:00
c05c688ee8 Avoid copying more handles than we have space for (#4564)
* Avoid copying more handles than we have space for

* Use locks instead

* Reduce nesting by combining the lock statements

* Add locks for other uses of _sessionHandles and _portHandles

* Use one object to lock instead of locking twice

* Release the lock as soon as possible
2023-03-19 11:30:04 +01:00
b2623dc27d OpenGL: Fix inverted conditional for counter flush from #4471 (#4560)
Fixes OpenGL.
2023-03-18 20:39:05 -03:00
5131b71437 Reducing memory allocations (#4537)
* add RecyclableMemoryStream dependency and MemoryStreamManager

* organize BinaryReader/BinaryWriter extensions

* add StreamExtensions to reduce need for BinaryWriter

* simple replacments of MemoryStream with RecyclableMemoryStream

* add write ReadOnlySequence<byte> support to IVirtualMemoryManager

* avoid 0-length array creation

* rework IpcMessage and related types to greatly reduce memory allocation by using RecylableMemoryStream, keeping streams around longer, avoiding their creation when possible, and avoiding creation of BinaryReader and BinaryWriter when possible

* reduce LINQ-induced memory allocations with custom methods to query KPriorityQueue

* use RecyclableMemoryStream in StreamUtils, and use StreamUtils in EmbeddedResources

* add constants for nanosecond/millisecond conversions

* code formatting

* XML doc adjustments

* fix: StreamExtension.WriteByte not writing non-zero values for lengths <= 16

* XML Doc improvements. Implement StreamExtensions.WriteByte() block writes for large-enough count values.

* add copyless path for StreamExtension.Write(ReadOnlySpan<int>)

* add default implementation of IVirtualMemoryManager.Write(ulong, ReadOnlySequence<byte>); remove previous explicit implementations

* code style fixes

* remove LINQ completely from KScheduler/KPriorityQueue by implementing a custom struct-based enumerator
2023-03-17 13:14:50 +01:00
7870423671 Update syscall capabilites to include SVCs from FW 15.0.0 (#4530)
* Add CapabilityType enum

* Add SupervisorCallCount

* kernel: Add CapabilityExtensions & Change type of capabilities to uint

* Remove private setter from Mask arrays

* Pass ReadOnlySpan directly & Remove redundant type casts
2023-03-17 12:55:19 +01:00
b72916fbc1 nuget: bump UnicornEngine.Unicorn (#4543)
Bumps [UnicornEngine.Unicorn](https://github.com/unicorn-engine/unicorn) from 2.0.2-rc1-f7c841d to 2.0.2-rc1-fb78016.
- [Release notes](https://github.com/unicorn-engine/unicorn/releases)
- [Changelog](https://github.com/unicorn-engine/unicorn/blob/master/ChangeLog)
- [Commits](https://github.com/unicorn-engine/unicorn/commits)

---
updated-dependencies:
- dependency-name: UnicornEngine.Unicorn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-17 12:50:52 +01:00
da073fce61 GPU: Fast path for adding one texture view to a group (#4528)
* GPU: Fast path for adding one texture view to a group

Texture group handles must store a list of their overlapping views, so they can be properly notified when a write is detected, and a few other things relating to texture readback. This is generally created when the group is established, with each handle looping over all views to find its overlaps. This whole process was also done when only a single view was added (and no handles were changed), however...

Sonic Frontiers had a huge cubemap array with 7350 faces (175 cubemaps * 6 faces * 7 levels), so iterating over both handles and existing views added up very fast. Since we are only adding a single view, we only need to _add_ that view to the existing overlaps, rather than recalculate them all.

This greatly improves performance during loading screens and a few seconds into gameplay on the "open zone" sections of Sonic Frontiers. May improve loading times or stutters on some other games.

Note that the current texture cache rules will cause these views to fall out of the cache, as there are more than the hard cap, so the cost will be repaid when reloading the open zone.

I also added some code to properly remove overlaps when texture views are removed, since it seems that was missing.

This can be improved further by only iterating handles that overlap the view (filter by range), but so can a few places in TextureGroup, so better to do all at once. The full generation of overlaps could probably be improved in a similar way.

I recommend testing a few games to make sure nothing breaks.

* Address feedback
2023-03-14 17:33:44 -03:00
1fc90e57d2 Update range for remapped sparse textures instead of recreating them (#4442)
* Update sparsely mapped texture ranges without recreating

Important TODO in TexturePool. Smaller TODO: should I look into making textures with views also do this? It needs to be able to detect if the views can be instantly deleted without issue if they're now remapped.

* Actually do partial updates

* Signal group dirty after mappings changed

* Fix various issues (should work now)

* Further optimisation

Should load a lot less data (16x) when partial updating 3d textures.

* Improve stability

* Allow granular uploads on large textures, improve rules

* Actually avoid updating slices that aren't modified.

* Address some feedback, minor optimisation

* Small tweak

* Refactor DereferenceRequest

More specific initialization methods.

* Improve code for resetting handles

* Explain data loading a bit more

* Add some safety for setting null from different threads.

All texture sets come from the one thread, but null sets can come from multiple. Only decrement ref count if we succeeded the null set first.

* Address feedback 1

* Make a bit safer
2023-03-14 17:08:44 -03:00
eafcc314a9 Ava UI: DownloadableContentManager Refactor (#4300)
* Start refactor

* Move around functions

* It builds

* Menu opens

* Buttons

* Fix overlapping text

* SaveAndClose and Close buttons

* Remove button

* Layout

* It’s a little funky but it works

* Enable all/disable all buttons

* Fix UpdateCount desyncs

* Search bar

* Search by title id

* Fix fuck ups

* Fix selection mode

* Update Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs

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

* Update Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs

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

* Update Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs

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

* Fix search bar

* Log corrupted DLC json

* Fix LibHac changes

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-03-14 17:04:38 +01:00
6e9bd4de13 GPU: Scale counter results before addition (#4471)
* GPU: Scale counter results before addition

Counter results were being scaled on ReportCounter, which meant that the _total_ value of the counter was being scaled. Not only could this result in very large numbers and weird overflows if the game doesn't clear the counter, but it also caused the result to change drastically.

This PR changes scaling to be done when the value is added to the counter on the backend. This should evaluate the scale at the same time as before, on report counter, but avoiding the issue with scaling the total.

Fixes scaling in Warioware, at least in the demo, where it seems to compare old/new counters and broke down when scaling was enabled.

* Fix issues when result is partially uploaded.

Drivers tend to write the low half first, then the high half. Retry if the high half is FFFFFFFF.
2023-03-12 18:01:15 +01:00
05a41b31bc Misc: Support space in path on macOS distribution (#4462)
* .

* Apply suggestions from code review

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* wildcard(*) needs to be outside of quotes(") for cp to work

---------

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-03-12 17:21:21 +01:00
eed17f963e Increase access permissions for Ava timezones (#4538) 2023-03-12 17:20:09 +01:00
c09c0c002d [Flatpak] Beautify multiline strings again & Add full git commit hash (#4535)
* Don't destroy multiline strings

* Use full git commit hash
2023-03-12 10:42:33 +01:00
d56d335c0b misc: Some dependencies cleanup (#4507)
* Remove dependencies on libraries provided by .NET standard library

* Use System.IO.Hashing instead of Crc32.NET
2023-03-12 03:24:11 +01:00
23c844b2aa Misc performance tweaks (#4509)
* use Array.Empty() where instead of allocating new zero-length arrays

* structure for loops in a way that the JIT will elide array/Span bounds checking

* avoiding function calls in for loop condition tests

* avoid LINQ in a hot path

* conform with code style

* fix mistake in GetNextWaitingObject()

* fix GetNextWaitingObject() possibility of returning null if all list items have TimePoint == long.MaxValue

* make GetNextWaitingObject() behave FIFO behavior for multiple items with the same TimePoint
2023-03-11 17:05:48 -03:00
81691b9e37 gha(release): Attempt to fix flathub pusher 2023-03-11 20:11:09 +01:00
2dc422bc14 gha(release): Hopefully fixes it 2023-03-11 19:16:08 +01:00
a80fa5e33f gha(release): Makes environment variables global 2023-03-11 19:09:48 +01:00
954e995321 Attempt to fix syntax error of previous merge 2023-03-11 19:06:42 +01:00
dad9ab6bb6 [Flatpak] Add release github workflow (#4529)
* Add flatpak release workflow

Co-authored-by: Mary <mary@mary.zone>

* infra: Update required SDK version to 7.0.200

---------

Co-authored-by: Mary <mary@mary.zone>
2023-03-11 19:04:13 +01:00
f0562b9c75 CPU: Avoid argument value copies on the JIT (#4484)
* Minor refactoring of the pre-allocator

* Avoid LoadArgument copies

* PPTC version bump
2023-03-08 23:25:35 +01:00
b8556530f2 nuget: bump Microsoft.CodeAnalysis.CSharp from 4.4.0 to 4.5.0 (#4488)
Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) from 4.4.0 to 4.5.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>
2023-03-04 15:37:08 +01:00
4f3af839be Minor code formatting (#4498) 2023-03-04 14:43:08 +01:00
155736c986 nuget: bump UnicornEngine.Unicorn (#4500)
Bumps [UnicornEngine.Unicorn](https://github.com/unicorn-engine/unicorn) from 2.0.2-rc1-a913199 to 2.0.2-rc1-f7c841d.
- [Release notes](https://github.com/unicorn-engine/unicorn/releases)
- [Changelog](https://github.com/unicorn-engine/unicorn/blob/master/ChangeLog)
- [Commits](https://github.com/unicorn-engine/unicorn/commits)

---
updated-dependencies:
- dependency-name: UnicornEngine.Unicorn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-04 11:32:30 +00:00
dba908dc78 Add post processing feature to the readme (#4499)
* Add post processing feature to the readme

Adds post processing information to the GPU section in the readme.

* correct "Anti-Aliasing"
2023-03-04 02:15:29 +00:00
ecee34a50c Update LibHac to 0.18.0 (#4414)
* Update LibHac to 0.18.0

* Change instance of AsBytes(CreateReadOnlySpan(...)) to AsReadOnlyByteSpan(...)
2023-03-01 23:42:27 -03:00
9b5a0c3889 Sockets: Properly convert error codes on MacOS (#4491)
* Sockets: Properly convert error codes on MacOS

The error codes for MacOS are very different to how they are on windows or linux. An alternate mapping is used when the host operating system is MacOS.

This PR also defaults IsDhcpEnabled to true when interfaceProperties.DhcpServerAddresses is not available.

This change was already in `macos1`.

* Address feedback
2023-02-28 03:41:44 +00:00
80b4972139 Add Support for Post Processing Effects (#3616)
* Add Post Processing Effects

* fix events and shader issues

* fix gtk upscale slider value

* fix bgra games

* don't swap swizzle if already swapped

* restore opengl texture state after effects run

* addressed review

* use single pipeline for smaa and fsr

* call finish on all pipelines

* addressed review

* attempt fix file case

* attempt fixing file case

* fix filter level tick frequency

* adjust filter slider margins

* replace fxaa shaders with original shader

* addressed review
2023-02-27 18:11:55 -03:00
5d85468302 Vulkan: Support list topology primitive restart (#4483) 2023-02-26 19:19:00 -03:00
9b1cc2cec6 Logging: Redirect StdErr into logging system (#4427)
* Logging: Redirect StdErr into logging system

* Remove Mono.Unix

* Apply suggestions from code review

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

* Address comments

---------

Co-authored-by: Mary <thog@protonmail.com>
Co-authored-by: riperiperi <rhy3756547@hotmail.com>
Co-authored-by: Mary <mary@mary.zone>
2023-02-25 15:07:23 +00:00
e691622f0a misc: Add missing DefineConstants definition in Ryujinx.Common
Fix flathub and nixpkgs build hopefully now.
2023-02-25 13:32:20 +01:00
f663a5cd38 macos: Add updater support (#4464)
This is a very basic updater but should be enough for now.

---------

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-02-25 12:30:48 +01:00
f7c2e867f4 chore: Update OpenTK to 4.7.7 (#4478) 2023-02-25 10:55:57 +00:00
cedd200745 Move gl_Layer to vertex shader if geometry is not supported (#4368)
* Set gl_Layer on vertex shader if it's set on the geometry shader and it does nothing else

* Shader cache version bump

* PR feedback

* Fix typo
2023-02-25 10:39:51 +00:00
58207685c0 Perform bounds checking before list indexer to avoid frequent exceptions (#4438)
* Perform bounds checking before list indexer to avoid frequent ArgumentOutOfRangeExceptions

* do a single compare after casting id and .Count to uint
2023-02-25 10:26:39 +00:00
095ad923ad Account for multisample when calculating render target size hint (#4467) 2023-02-23 10:08:54 +01:00
327 changed files with 28982 additions and 4079 deletions

171
.github/workflows/flatpak.yml vendored Normal file
View File

@ -0,0 +1,171 @@
name: Flatpak release job
on:
workflow_call:
inputs:
ryujinx_version:
required: true
type: string
concurrency: flatpak-release
jobs:
release:
runs-on: ubuntu-latest
env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
GIT_COMMITTER_NAME: "RyujinxBot"
GIT_COMMITTER_EMAIL: "61127645+RyujinxBot@users.noreply.github.com"
RYUJINX_PROJECT_FILE: "Ryujinx/Ryujinx.csproj"
NUGET_SOURCES_DESTDIR: "nuget-sources"
RYUJINX_VERSION: "${{ inputs.ryujinx_version }}"
steps:
- uses: actions/checkout@v3
with:
path: Ryujinx
- uses: actions/setup-dotnet@v3
with:
global-json-file: Ryujinx/global.json
- name: Get version info
id: version_info
working-directory: Ryujinx
run: |
echo "git_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- uses: actions/checkout@v3
with:
repository: flathub/org.ryujinx.Ryujinx
token: ${{ secrets.RYUJINX_BOT_PAT }}
submodules: recursive
path: flathub
- name: Install dependencies
run: python -m pip install PyYAML lxml
- name: Restore Nuget packages
run: dotnet restore Ryujinx/${{ env.RYUJINX_PROJECT_FILE }}
- name: Generate nuget_sources.json
shell: python
run: |
from pathlib import Path
import base64
import binascii
import json
import os
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')
sources.append({
'type': 'file',
'url': url,
'sha512': sha512,
'dest': os.environ['NUGET_SOURCES_DESTDIR'],
'dest-filename': filename,
})
with open('flathub/nuget_sources.json', 'w') as fp:
json.dump(sources, fp, indent=4)
- name: Update flatpak metadata
id: metadata
env:
RYUJINX_GIT_HASH: ${{ steps.version_info.outputs.git_hash }}
shell: python
run: |
import hashlib
import hmac
import json
import os
import yaml
from datetime import datetime
from lxml import etree
# Ensure we don't destroy multiline strings
def str_presenter(dumper, data):
if len(data.splitlines()) > 1:
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
yaml.representer.SafeRepresenter.add_representer(str, str_presenter)
yaml_file = "flathub/org.ryujinx.Ryujinx.yml"
xml_file = "flathub/org.ryujinx.Ryujinx.appdata.xml"
with open(yaml_file, "r") as f:
data = yaml.safe_load(f)
for source in data["modules"][0]["sources"]:
if type(source) is str:
continue
if (
source["type"] == "git"
and source["url"] == "https://github.com/Ryujinx/Ryujinx.git"
):
source["commit"] = os.environ['RYUJINX_GIT_HASH']
is_same_version = data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] == os.environ['RYUJINX_VERSION']
with open(os.environ['GITHUB_OUTPUT'], "a") as gh_out:
if is_same_version:
gh_out.write(f"commit_message=Retry update to {os.environ['RYUJINX_VERSION']}")
else:
gh_out.write(f"commit_message=Update to {os.environ['RYUJINX_VERSION']}")
if not is_same_version:
data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] = os.environ['RYUJINX_VERSION']
with open(yaml_file, "w") as f:
yaml.safe_dump(data, f, sort_keys=False)
parser = etree.XMLParser(remove_blank_text=True)
tree = etree.parse(xml_file, parser)
root = tree.getroot()
releases = root.find("releases")
element = etree.Element("release")
element.set("version", os.environ['RYUJINX_VERSION'])
element.set("date", datetime.now().date().isoformat())
releases.insert(0, element)
# Ensure 4 spaces
etree.indent(root, space=" ")
with open(xml_file, "wb") as f:
f.write(
etree.tostring(
tree,
pretty_print=True,
encoding="UTF-8",
doctype='<?xml version="1.0" encoding="UTF-8"?>',
)
)
- name: Push flatpak update
working-directory: flathub
env:
COMMIT_MESSAGE: ${{ steps.metadata.outputs.commit_message }}
run: |
git config user.name "${{ env.GIT_COMMITTER_NAME }}"
git config user.email "${{ env.GIT_COMMITTER_EMAIL }}"
git add .
git commit -m "$COMMIT_MESSAGE"
git push origin master

View File

@ -13,23 +13,22 @@ on:
concurrency: release
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.1"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
jobs:
release:
runs-on: windows-latest
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.1"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
steps:
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
global-json-file: global.json
- name: Get version info
id: version_info
run: |
@ -112,3 +111,21 @@ jobs:
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }}
- name: Create tag
uses: actions/github-script@v5
with:
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}',
sha: context.sha
})
flatpak_release:
uses: ./.github/workflows/flatpak.yml
needs: release
with:
ryujinx_version: "1.1.${{ github.run_number }}"
secrets: inherit

View File

@ -7,6 +7,7 @@
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -1,6 +1,7 @@
using ARMeilleure.CodeGen.Linking;
using ARMeilleure.CodeGen.RegisterAllocators;
using ARMeilleure.IntermediateRepresentation;
using Ryujinx.Common.Memory;
using System;
using System.Collections.Generic;
using System.IO;
@ -59,7 +60,7 @@ namespace ARMeilleure.CodeGen.Arm64
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
{
_stream = new MemoryStream();
_stream = MemoryStreamManager.Shared.GetStream();
AllocResult = allocResult;
@ -265,7 +266,7 @@ namespace ARMeilleure.CodeGen.Arm64
}
else
{
relocInfo = new RelocInfo(new RelocEntry[0]);
relocInfo = new RelocInfo(Array.Empty<RelocEntry>());
}
return (code, relocInfo);

View File

@ -9,7 +9,7 @@ using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
namespace ARMeilleure.CodeGen.Arm64
{
class PreAllocator
static class PreAllocator
{
private class ConstantDict
{
@ -54,8 +54,8 @@ namespace ARMeilleure.CodeGen.Arm64
continue;
}
HandleConstantRegCopy(constants, block.Operations, node);
HandleDestructiveRegCopy(block.Operations, node);
InsertConstantRegCopies(constants, block.Operations, node);
InsertDestructiveRegCopies(block.Operations, node);
switch (node.Instruction)
{
@ -78,28 +78,28 @@ namespace ARMeilleure.CodeGen.Arm64
// Copy values to registers expected by the function
// being called, as mandated by the ABI.
HandleCall(constants, block.Operations, node);
InsertCallCopies(constants, block.Operations, node);
break;
case Instruction.CompareAndSwap:
case Instruction.CompareAndSwap16:
case Instruction.CompareAndSwap8:
nextNode = HandleCompareAndSwap(block.Operations, node);
nextNode = GenerateCompareAndSwap(block.Operations, node);
break;
case Instruction.LoadArgument:
nextNode = HandleLoadArgument(cctx, ref buffer, block.Operations, preservedArgs, node);
nextNode = InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
break;
case Instruction.Return:
HandleReturn(block.Operations, node);
InsertReturnCopy(block.Operations, node);
break;
case Instruction.Tailcall:
HandleTailcall(constants, block.Operations, stackAlloc, node, node);
InsertTailcallCopies(constants, block.Operations, stackAlloc, node, node);
break;
}
}
}
}
private static void HandleConstantRegCopy(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
private static void InsertConstantRegCopies(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
{
if (node.SourcesCount == 0 || IsIntrinsicWithConst(node))
{
@ -211,7 +211,7 @@ namespace ARMeilleure.CodeGen.Arm64
}
}
private static void HandleDestructiveRegCopy(IntrusiveList<Operation> nodes, Operation node)
private static void InsertDestructiveRegCopies(IntrusiveList<Operation> nodes, Operation node)
{
if (node.Destination == default || node.SourcesCount == 0)
{
@ -259,7 +259,7 @@ namespace ARMeilleure.CodeGen.Arm64
}
}
private static void HandleCall(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
private static void InsertCallCopies(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
{
Operation operation = node;
@ -319,7 +319,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operation copyOp = Operation(Instruction.Copy, argReg, source);
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, copyOp));
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp));
sources.Add(argReg);
}
@ -329,7 +329,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, spillOp));
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, spillOp));
stackOffset += source.Type.GetSizeInBytes();
}
@ -364,7 +364,7 @@ namespace ARMeilleure.CodeGen.Arm64
operation.SetSources(sources.ToArray());
}
private static void HandleTailcall(
private static void InsertTailcallCopies(
ConstantDict constants,
IntrusiveList<Operation> nodes,
StackAllocator stackAlloc,
@ -420,7 +420,7 @@ namespace ARMeilleure.CodeGen.Arm64
Operation copyOp = Operation(Instruction.Copy, argReg, source);
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, copyOp));
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp));
sources.Add(argReg);
}
@ -444,7 +444,7 @@ namespace ARMeilleure.CodeGen.Arm64
operation.SetSources(sources.ToArray());
}
private static Operation HandleCompareAndSwap(IntrusiveList<Operation> nodes, Operation node)
private static Operation GenerateCompareAndSwap(IntrusiveList<Operation> nodes, Operation node)
{
Operand expected = node.GetSource(1);
@ -508,7 +508,7 @@ namespace ARMeilleure.CodeGen.Arm64
return node.ListNext;
}
private static void HandleReturn(IntrusiveList<Operation> nodes, Operation node)
private static void InsertReturnCopy(IntrusiveList<Operation> nodes, Operation node)
{
if (node.SourcesCount == 0)
{
@ -537,7 +537,7 @@ namespace ARMeilleure.CodeGen.Arm64
}
}
private static Operation HandleLoadArgument(
private static Operation InsertLoadArgumentCopy(
CompilerContext cctx,
ref Span<Operation> buffer,
IntrusiveList<Operation> nodes,
@ -629,7 +629,7 @@ namespace ARMeilleure.CodeGen.Arm64
if (dest.AssignmentsCount == 1)
{
// Let's propagate the argument if we can to avoid copies.
Propagate(ref buffer, dest, preservedArgs[index]);
PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]);
nextNode = node.ListNext;
}
else
@ -648,54 +648,6 @@ namespace ARMeilleure.CodeGen.Arm64
}
}
private static void Propagate(ref Span<Operation> buffer, Operand dest, Operand value)
{
ReadOnlySpan<Operation> uses = dest.GetUses(ref buffer);
foreach (Operation use in uses)
{
for (int srcIndex = 0; srcIndex < use.SourcesCount; srcIndex++)
{
Operand useSrc = use.GetSource(srcIndex);
if (useSrc == dest)
{
use.SetSource(srcIndex, value);
}
else if (useSrc.Kind == OperandKind.Memory)
{
MemoryOperand memoryOp = useSrc.GetMemory();
Operand baseAddr = memoryOp.BaseAddress;
Operand index = memoryOp.Index;
bool changed = false;
if (baseAddr == dest)
{
baseAddr = value;
changed = true;
}
if (index == dest)
{
index = value;
changed = true;
}
if (changed)
{
use.SetSource(srcIndex, MemoryOp(
useSrc.Type,
baseAddr,
index,
memoryOp.Scale,
memoryOp.Displacement));
}
}
}
}
}
private static Operand AddFloatConstantCopy(
ConstantDict constants,
IntrusiveList<Operation> nodes,

View File

@ -0,0 +1,57 @@
using ARMeilleure.IntermediateRepresentation;
using System;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
namespace ARMeilleure.CodeGen
{
static class PreAllocatorCommon
{
public static void Propagate(ref Span<Operation> buffer, Operand dest, Operand value)
{
ReadOnlySpan<Operation> uses = dest.GetUses(ref buffer);
foreach (Operation use in uses)
{
for (int srcIndex = 0; srcIndex < use.SourcesCount; srcIndex++)
{
Operand useSrc = use.GetSource(srcIndex);
if (useSrc == dest)
{
use.SetSource(srcIndex, value);
}
else if (useSrc.Kind == OperandKind.Memory)
{
MemoryOperand memoryOp = useSrc.GetMemory();
Operand baseAddr = memoryOp.BaseAddress;
Operand index = memoryOp.Index;
bool changed = false;
if (baseAddr == dest)
{
baseAddr = value;
changed = true;
}
if (index == dest)
{
index = value;
changed = true;
}
if (changed)
{
use.SetSource(srcIndex, MemoryOp(
useSrc.Type,
baseAddr,
index,
memoryOp.Scale,
memoryOp.Displacement));
}
}
}
}
}
}
}

View File

@ -433,16 +433,11 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
private static int GetHighestValueIndex(Span<int> span)
{
int highest = span[0];
if (highest == int.MaxValue)
{
return 0;
}
int highest = int.MinValue;
int selected = 0;
for (int index = 1; index < span.Length; index++)
for (int index = 0; index < span.Length; index++)
{
int current = span[index];

View File

@ -1,5 +1,6 @@
using ARMeilleure.CodeGen.Linking;
using ARMeilleure.IntermediateRepresentation;
using Ryujinx.Common.Memory;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -1033,7 +1034,13 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(opCode != BadOp, "Invalid opcode value.");
if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding)
if ((flags & InstructionFlags.Evex) != 0 && HardwareCapabilities.SupportsEvexEncoding)
{
WriteEvexInst(dest, src1, src2, type, flags, opCode);
opCode &= 0xff;
}
else if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding)
{
// In a vex encoding, only one prefix can be active at a time. The active prefix is encoded in the second byte using two bits.
@ -1152,6 +1159,103 @@ namespace ARMeilleure.CodeGen.X86
}
}
private void WriteEvexInst(
Operand dest,
Operand src1,
Operand src2,
OperandType type,
InstructionFlags flags,
int opCode,
bool broadcast = false,
int registerWidth = 128,
int maskRegisterIdx = 0,
bool zeroElements = false)
{
int op1Idx = dest.GetRegister().Index;
int op2Idx = src1.GetRegister().Index;
int op3Idx = src2.GetRegister().Index;
WriteByte(0x62);
// P0
// Extend operand 1 register
bool r = (op1Idx & 8) == 0;
// Extend operand 3 register
bool x = (op3Idx & 16) == 0;
// Extend operand 3 register
bool b = (op3Idx & 8) == 0;
// Extend operand 1 register
bool rp = (op1Idx & 16) == 0;
// Escape code index
byte mm = 0b00;
switch ((ushort)(opCode >> 8))
{
case 0xf00: mm = 0b01; break;
case 0xf38: mm = 0b10; break;
case 0xf3a: mm = 0b11; break;
default: Debug.Fail($"Failed to EVEX encode opcode 0x{opCode:X}."); break;
}
WriteByte(
(byte)(
(r ? 0x80 : 0) |
(x ? 0x40 : 0) |
(b ? 0x20 : 0) |
(rp ? 0x10 : 0) |
mm));
// P1
// Specify 64-bit lane mode
bool w = Is64Bits(type);
// Operand 2 register index
byte vvvv = (byte)(~op2Idx & 0b1111);
// Opcode prefix
byte pp = (flags & InstructionFlags.PrefixMask) switch
{
InstructionFlags.Prefix66 => 0b01,
InstructionFlags.PrefixF3 => 0b10,
InstructionFlags.PrefixF2 => 0b11,
_ => 0
};
WriteByte(
(byte)(
(w ? 0x80 : 0) |
(vvvv << 3) |
0b100 |
pp));
// P2
// Mask register determines what elements to zero, rather than what elements to merge
bool z = zeroElements;
// Specifies register-width
byte ll = 0b00;
switch (registerWidth)
{
case 128: ll = 0b00; break;
case 256: ll = 0b01; break;
case 512: ll = 0b10; break;
default: Debug.Fail($"Invalid EVEX vector register width {registerWidth}."); break;
}
// Embedded broadcast in the case of a memory operand
bool bcast = broadcast;
// Extend operand 2 register
bool vp = (op2Idx & 16) == 0;
// Mask register index
Debug.Assert(maskRegisterIdx < 8, $"Invalid mask register index {maskRegisterIdx}.");
byte aaa = (byte)(maskRegisterIdx & 0b111);
WriteByte(
(byte)(
(z ? 0x80 : 0) |
(ll << 5) |
(bcast ? 0x10 : 0) |
(vp ? 8 : 0) |
aaa));
}
private void WriteCompactInst(Operand operand, int opCode)
{
int regIndex = operand.GetRegister().Index;
@ -1285,7 +1389,7 @@ namespace ARMeilleure.CodeGen.X86
// Write the code, ignoring the dummy bytes after jumps, into a new stream.
_stream.Seek(0, SeekOrigin.Begin);
using var codeStream = new MemoryStream();
using var codeStream = MemoryStreamManager.Shared.GetStream();
var assembler = new Assembler(codeStream, HasRelocs);
bool hasRelocs = HasRelocs;

View File

@ -20,6 +20,7 @@ namespace ARMeilleure.CodeGen.X86
Reg8Dest = 1 << 2,
RexW = 1 << 3,
Vex = 1 << 4,
Evex = 1 << 5,
PrefixBit = 16,
PrefixMask = 7 << PrefixBit,
@ -278,6 +279,7 @@ namespace ARMeilleure.CodeGen.X86
Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vpternlogd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a25, InstructionFlags.Evex | InstructionFlags.Prefix66));
Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None));
Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex));

View File

@ -1,5 +1,6 @@
using ARMeilleure.CodeGen.RegisterAllocators;
using ARMeilleure.IntermediateRepresentation;
using Ryujinx.Common.Memory;
using System.IO;
using System.Numerics;
@ -22,7 +23,7 @@ namespace ARMeilleure.CodeGen.X86
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
{
_stream = new MemoryStream();
_stream = MemoryStreamManager.Shared.GetStream();
_blockLabels = new Operand[blocksCount];
AllocResult = allocResult;

View File

@ -1,10 +1,14 @@
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
namespace ARMeilleure.CodeGen.X86
{
static class HardwareCapabilities
{
private delegate uint GetXcr0();
static HardwareCapabilities()
{
if (!X86Base.IsSupported)
@ -24,6 +28,34 @@ namespace ARMeilleure.CodeGen.X86
FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7;
FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7;
}
Xcr0InfoEax = (Xcr0FlagsEax)GetXcr0Eax();
}
private static uint GetXcr0Eax()
{
if (!FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave))
{
// XSAVE feature required for xgetbv
return 0;
}
ReadOnlySpan<byte> asmGetXcr0 = new byte[]
{
0x31, 0xc9, // xor ecx, ecx
0xf, 0x01, 0xd0, // xgetbv
0xc3, // ret
};
using MemoryBlock memGetXcr0 = new MemoryBlock((ulong)asmGetXcr0.Length);
memGetXcr0.Write(0, asmGetXcr0);
memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute);
var fGetXcr0 = Marshal.GetDelegateForFunctionPointer<GetXcr0>(memGetXcr0.Pointer);
return fGetXcr0();
}
[Flags]
@ -44,6 +76,8 @@ namespace ARMeilleure.CodeGen.X86
Sse42 = 1 << 20,
Popcnt = 1 << 23,
Aes = 1 << 25,
Xsave = 1 << 26,
Osxsave = 1 << 27,
Avx = 1 << 28,
F16c = 1 << 29
}
@ -52,7 +86,11 @@ namespace ARMeilleure.CodeGen.X86
public enum FeatureFlags7Ebx
{
Avx2 = 1 << 5,
Sha = 1 << 29
Avx512f = 1 << 16,
Avx512dq = 1 << 17,
Sha = 1 << 29,
Avx512bw = 1 << 30,
Avx512vl = 1 << 31
}
[Flags]
@ -61,10 +99,21 @@ namespace ARMeilleure.CodeGen.X86
Gfni = 1 << 8,
}
[Flags]
public enum Xcr0FlagsEax
{
Sse = 1 << 1,
YmmHi128 = 1 << 2,
Opmask = 1 << 5,
ZmmHi256 = 1 << 6,
Hi16Zmm = 1 << 7
}
public static FeatureFlags1Edx FeatureInfo1Edx { get; }
public static FeatureFlags1Ecx FeatureInfo1Ecx { get; }
public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0;
public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0;
public static Xcr0FlagsEax Xcr0InfoEax { get; } = 0;
public static bool SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse);
public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2);
@ -76,8 +125,13 @@ namespace ARMeilleure.CodeGen.X86
public static bool SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42);
public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt);
public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes);
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx);
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx | FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128);
public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx;
public static bool SupportsAvx512F => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512f) && FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave)
&& Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128 | Xcr0FlagsEax.Opmask | Xcr0FlagsEax.ZmmHi256 | Xcr0FlagsEax.Hi16Zmm);
public static bool SupportsAvx512Vl => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512vl) && SupportsAvx512F;
public static bool SupportsAvx512Bw => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512bw) && SupportsAvx512F;
public static bool SupportsAvx512Dq => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512dq) && SupportsAvx512F;
public static bool SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c);
public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha);
public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni);
@ -85,5 +139,6 @@ namespace ARMeilleure.CodeGen.X86
public static bool ForceLegacySse { get; set; }
public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse;
public static bool SupportsEvexEncoding => SupportsAvx512F && !ForceLegacySse;
}
}

View File

@ -180,6 +180,7 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma));
Add(Intrinsic.X86Vpternlogd, new IntrinsicInfo(X86Instruction.Vpternlogd, IntrinsicType.TernaryImm));
Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary));
Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary));
}

View File

@ -2,19 +2,20 @@ using ARMeilleure.CodeGen.RegisterAllocators;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
namespace ARMeilleure.CodeGen.X86
{
static class PreAllocator
class PreAllocator
{
public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs)
{
maxCallArgs = -1;
Span<Operation> buffer = default;
CallConvName callConv = CallingConvention.GetCurrentCallConv();
Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()];
@ -32,9 +33,9 @@ namespace ARMeilleure.CodeGen.X86
continue;
}
HandleConstantRegCopy(block.Operations, node);
HandleDestructiveRegCopy(block.Operations, node);
HandleConstrainedRegCopy(block.Operations, node);
InsertConstantRegCopies(block.Operations, node);
InsertDestructiveRegCopies(block.Operations, node);
InsertConstrainedRegCopies(block.Operations, node);
switch (node.Instruction)
{
@ -59,62 +60,62 @@ namespace ARMeilleure.CodeGen.X86
// being called, as mandated by the ABI.
if (callConv == CallConvName.Windows)
{
HandleCallWindowsAbi(block.Operations, stackAlloc, node);
PreAllocatorWindows.InsertCallCopies(block.Operations, stackAlloc, node);
}
else /* if (callConv == CallConvName.SystemV) */
{
HandleCallSystemVAbi(block.Operations, node);
PreAllocatorSystemV.InsertCallCopies(block.Operations, node);
}
break;
case Instruction.ConvertToFPUI:
HandleConvertToFPUI(block.Operations, node);
GenerateConvertToFPUI(block.Operations, node);
break;
case Instruction.LoadArgument:
if (callConv == CallConvName.Windows)
{
nextNode = HandleLoadArgumentWindowsAbi(cctx, block.Operations, preservedArgs, node);
nextNode = PreAllocatorWindows.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
}
else /* if (callConv == CallConvName.SystemV) */
{
nextNode = HandleLoadArgumentSystemVAbi(cctx, block.Operations, preservedArgs, node);
nextNode = PreAllocatorSystemV.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
}
break;
case Instruction.Negate:
if (!node.GetSource(0).Type.IsInteger())
{
HandleNegate(block.Operations, node);
GenerateNegate(block.Operations, node);
}
break;
case Instruction.Return:
if (callConv == CallConvName.Windows)
{
HandleReturnWindowsAbi(cctx, block.Operations, preservedArgs, node);
PreAllocatorWindows.InsertReturnCopy(cctx, block.Operations, preservedArgs, node);
}
else /* if (callConv == CallConvName.SystemV) */
{
HandleReturnSystemVAbi(block.Operations, node);
PreAllocatorSystemV.InsertReturnCopy(block.Operations, node);
}
break;
case Instruction.Tailcall:
if (callConv == CallConvName.Windows)
{
HandleTailcallWindowsAbi(block.Operations, stackAlloc, node);
PreAllocatorWindows.InsertTailcallCopies(block.Operations, stackAlloc, node);
}
else
{
HandleTailcallSystemVAbi(block.Operations, stackAlloc, node);
PreAllocatorSystemV.InsertTailcallCopies(block.Operations, stackAlloc, node);
}
break;
case Instruction.VectorInsert8:
if (!HardwareCapabilities.SupportsSse41)
{
HandleVectorInsert8(block.Operations, node);
GenerateVectorInsert8(block.Operations, node);
}
break;
@ -131,7 +132,7 @@ namespace ARMeilleure.CodeGen.X86
}
}
private static void HandleConstantRegCopy(IntrusiveList<Operation> nodes, Operation node)
protected static void InsertConstantRegCopies(IntrusiveList<Operation> nodes, Operation node)
{
if (node.SourcesCount == 0 || IsXmmIntrinsic(node))
{
@ -212,7 +213,7 @@ namespace ARMeilleure.CodeGen.X86
}
}
private static void HandleConstrainedRegCopy(IntrusiveList<Operation> nodes, Operation node)
protected static void InsertConstrainedRegCopies(IntrusiveList<Operation> nodes, Operation node)
{
Operand dest = node.Destination;
@ -369,7 +370,7 @@ namespace ARMeilleure.CodeGen.X86
}
}
private static void HandleDestructiveRegCopy(IntrusiveList<Operation> nodes, Operation node)
protected static void InsertDestructiveRegCopies(IntrusiveList<Operation> nodes, Operation node)
{
if (node.Destination == default || node.SourcesCount == 0)
{
@ -447,7 +448,7 @@ namespace ARMeilleure.CodeGen.X86
}
}
private static void HandleConvertToFPUI(IntrusiveList<Operation> nodes, Operation node)
private static void GenerateConvertToFPUI(IntrusiveList<Operation> nodes, Operation node)
{
// Unsigned integer to FP conversions are not supported on X86.
// We need to turn them into signed integer to FP conversions, and
@ -501,7 +502,7 @@ namespace ARMeilleure.CodeGen.X86
Delete(nodes, currentNode);
}
private static void HandleNegate(IntrusiveList<Operation> nodes, Operation node)
private static void GenerateNegate(IntrusiveList<Operation> nodes, Operation node)
{
// There's no SSE FP negate instruction, so we need to transform that into
// a XOR of the value to be negated with a mask with the highest bit set.
@ -534,7 +535,7 @@ namespace ARMeilleure.CodeGen.X86
Delete(nodes, currentNode);
}
private static void HandleVectorInsert8(IntrusiveList<Operation> nodes, Operation node)
private static void GenerateVectorInsert8(IntrusiveList<Operation> nodes, Operation node)
{
// Handle vector insertion, when SSE 4.1 is not supported.
Operand dest = node.Destination;
@ -579,620 +580,7 @@ namespace ARMeilleure.CodeGen.X86
Delete(nodes, currentNode);
}
private static void HandleCallWindowsAbi(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
{
Operand dest = node.Destination;
// Handle struct arguments.
int retArgs = 0;
int stackAllocOffset = 0;
int AllocateOnStack(int size)
{
// We assume that the stack allocator is initially empty (TotalSize = 0).
// Taking that into account, we can reuse the space allocated for other
// calls by keeping track of our own allocated size (stackAllocOffset).
// If the space allocated is not big enough, then we just expand it.
int offset = stackAllocOffset;
if (stackAllocOffset + size > stackAlloc.TotalSize)
{
stackAlloc.Allocate((stackAllocOffset + size) - stackAlloc.TotalSize);
}
stackAllocOffset += size;
return offset;
}
Operand arg0Reg = default;
if (dest != default && dest.Type == OperandType.V128)
{
int stackOffset = AllocateOnStack(dest.Type.GetSizeInBytes());
arg0Reg = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
Operation allocOp = Operation(Instruction.StackAlloc, arg0Reg, Const(stackOffset));
nodes.AddBefore(node, allocOp);
retArgs = 1;
}
int argsCount = node.SourcesCount - 1;
int maxArgs = CallingConvention.GetArgumentsOnRegsCount() - retArgs;
if (argsCount > maxArgs)
{
argsCount = maxArgs;
}
Operand[] sources = new Operand[1 + retArgs + argsCount];
sources[0] = node.GetSource(0);
if (arg0Reg != default)
{
sources[1] = arg0Reg;
}
for (int index = 1; index < node.SourcesCount; index++)
{
Operand source = node.GetSource(index);
if (source.Type == OperandType.V128)
{
Operand stackAddr = Local(OperandType.I64);
int stackOffset = AllocateOnStack(source.Type.GetSizeInBytes());
nodes.AddBefore(node, Operation(Instruction.StackAlloc, stackAddr, Const(stackOffset)));
Operation storeOp = Operation(Instruction.Store, default, stackAddr, source);
HandleConstantRegCopy(nodes, nodes.AddBefore(node, storeOp));
node.SetSource(index, stackAddr);
}
}
// Handle arguments passed on registers.
for (int index = 0; index < argsCount; index++)
{
Operand source = node.GetSource(index + 1);
Operand argReg;
int argIndex = index + retArgs;
if (source.Type.IsInteger())
{
argReg = Gpr(CallingConvention.GetIntArgumentRegister(argIndex), source.Type);
}
else
{
argReg = Xmm(CallingConvention.GetVecArgumentRegister(argIndex), source.Type);
}
Operation copyOp = Operation(Instruction.Copy, argReg, source);
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
sources[1 + retArgs + index] = argReg;
}
// The remaining arguments (those that are not passed on registers)
// should be passed on the stack, we write them to the stack with "SpillArg".
for (int index = argsCount; index < node.SourcesCount - 1; index++)
{
Operand source = node.GetSource(index + 1);
Operand offset = Const((index + retArgs) * 8);
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
HandleConstantRegCopy(nodes, nodes.AddBefore(node, spillOp));
}
if (dest != default)
{
if (dest.Type == OperandType.V128)
{
Operand retValueAddr = Local(OperandType.I64);
nodes.AddBefore(node, Operation(Instruction.Copy, retValueAddr, arg0Reg));
Operation loadOp = Operation(Instruction.Load, dest, retValueAddr);
nodes.AddAfter(node, loadOp);
node.Destination = default;
}
else
{
Operand retReg = dest.Type.IsInteger()
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
nodes.AddAfter(node, copyOp);
node.Destination = retReg;
}
}
node.SetSources(sources);
}
private static void HandleCallSystemVAbi(IntrusiveList<Operation> nodes, Operation node)
{
Operand dest = node.Destination;
List<Operand> sources = new List<Operand>
{
node.GetSource(0)
};
int argsCount = node.SourcesCount - 1;
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
int intCount = 0;
int vecCount = 0;
int stackOffset = 0;
for (int index = 0; index < argsCount; index++)
{
Operand source = node.GetSource(index + 1);
bool passOnReg;
if (source.Type.IsInteger())
{
passOnReg = intCount < intMax;
}
else if (source.Type == OperandType.V128)
{
passOnReg = intCount + 1 < intMax;
}
else
{
passOnReg = vecCount < vecMax;
}
if (source.Type == OperandType.V128 && passOnReg)
{
// V128 is a struct, we pass each half on a GPR if possible.
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
continue;
}
if (passOnReg)
{
Operand argReg = source.Type.IsInteger()
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
Operation copyOp = Operation(Instruction.Copy, argReg, source);
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
sources.Add(argReg);
}
else
{
Operand offset = Const(stackOffset);
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
HandleConstantRegCopy(nodes, nodes.AddBefore(node, spillOp));
stackOffset += source.Type.GetSizeInBytes();
}
}
node.SetSources(sources.ToArray());
if (dest != default)
{
if (dest.Type == OperandType.V128)
{
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
Operation operation = node;
node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg));
nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1)));
operation.Destination = default;
}
else
{
Operand retReg = dest.Type.IsInteger()
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
nodes.AddAfter(node, copyOp);
node.Destination = retReg;
}
}
}
private static void HandleTailcallSystemVAbi(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
{
List<Operand> sources = new List<Operand>
{
node.GetSource(0)
};
int argsCount = node.SourcesCount - 1;
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
int intCount = 0;
int vecCount = 0;
// Handle arguments passed on registers.
for (int index = 0; index < argsCount; index++)
{
Operand source = node.GetSource(1 + index);
bool passOnReg;
if (source.Type.IsInteger())
{
passOnReg = intCount + 1 < intMax;
}
else
{
passOnReg = vecCount < vecMax;
}
if (source.Type == OperandType.V128 && passOnReg)
{
// V128 is a struct, we pass each half on a GPR if possible.
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
continue;
}
if (passOnReg)
{
Operand argReg = source.Type.IsInteger()
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
Operation copyOp = Operation(Instruction.Copy, argReg, source);
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
sources.Add(argReg);
}
else
{
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
}
}
// The target address must be on the return registers, since we
// don't return anything and it is guaranteed to not be a
// callee saved register (which would be trashed on the epilogue).
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
nodes.AddBefore(node, addrCopyOp);
sources[0] = retReg;
node.SetSources(sources.ToArray());
}
private static void HandleTailcallWindowsAbi(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
{
int argsCount = node.SourcesCount - 1;
int maxArgs = CallingConvention.GetArgumentsOnRegsCount();
if (argsCount > maxArgs)
{
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
}
Operand[] sources = new Operand[1 + argsCount];
// Handle arguments passed on registers.
for (int index = 0; index < argsCount; index++)
{
Operand source = node.GetSource(1 + index);
Operand argReg = source.Type.IsInteger()
? Gpr(CallingConvention.GetIntArgumentRegister(index), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(index), source.Type);
Operation copyOp = Operation(Instruction.Copy, argReg, source);
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
sources[1 + index] = argReg;
}
// The target address must be on the return registers, since we
// don't return anything and it is guaranteed to not be a
// callee saved register (which would be trashed on the epilogue).
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
nodes.AddBefore(node, addrCopyOp);
sources[0] = retReg;
node.SetSources(sources);
}
private static Operation HandleLoadArgumentWindowsAbi(
CompilerContext cctx,
IntrusiveList<Operation> nodes,
Operand[] preservedArgs,
Operation node)
{
Operand source = node.GetSource(0);
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
int retArgs = cctx.FuncReturnType == OperandType.V128 ? 1 : 0;
int index = source.AsInt32() + retArgs;
if (index < CallingConvention.GetArgumentsOnRegsCount())
{
Operand dest = node.Destination;
if (preservedArgs[index] == default)
{
Operand argReg, pArg;
if (dest.Type.IsInteger())
{
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), dest.Type);
pArg = Local(dest.Type);
}
else if (dest.Type == OperandType.V128)
{
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), OperandType.I64);
pArg = Local(OperandType.I64);
}
else
{
argReg = Xmm(CallingConvention.GetVecArgumentRegister(index), dest.Type);
pArg = Local(dest.Type);
}
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
preservedArgs[index] = pArg;
}
Operation argCopyOp = Operation(dest.Type == OperandType.V128
? Instruction.Load
: Instruction.Copy, dest, preservedArgs[index]);
Operation newNode = nodes.AddBefore(node, argCopyOp);
Delete(nodes, node);
return newNode;
}
else
{
// TODO: Pass on stack.
return node;
}
}
private static Operation HandleLoadArgumentSystemVAbi(
CompilerContext cctx,
IntrusiveList<Operation> nodes,
Operand[] preservedArgs,
Operation node)
{
Operand source = node.GetSource(0);
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
int index = source.AsInt32();
int intCount = 0;
int vecCount = 0;
for (int cIndex = 0; cIndex < index; cIndex++)
{
OperandType argType = cctx.FuncArgTypes[cIndex];
if (argType.IsInteger())
{
intCount++;
}
else if (argType == OperandType.V128)
{
intCount += 2;
}
else
{
vecCount++;
}
}
bool passOnReg;
if (source.Type.IsInteger())
{
passOnReg = intCount < CallingConvention.GetIntArgumentsOnRegsCount();
}
else if (source.Type == OperandType.V128)
{
passOnReg = intCount + 1 < CallingConvention.GetIntArgumentsOnRegsCount();
}
else
{
passOnReg = vecCount < CallingConvention.GetVecArgumentsOnRegsCount();
}
if (passOnReg)
{
Operand dest = node.Destination;
if (preservedArgs[index] == default)
{
if (dest.Type == OperandType.V128)
{
// V128 is a struct, we pass each half on a GPR if possible.
Operand pArg = Local(OperandType.V128);
Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64);
Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64);
Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg);
Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1));
cctx.Cfg.Entry.Operations.AddFirst(copyH);
cctx.Cfg.Entry.Operations.AddFirst(copyL);
preservedArgs[index] = pArg;
}
else
{
Operand pArg = Local(dest.Type);
Operand argReg = dest.Type.IsInteger()
? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type);
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
preservedArgs[index] = pArg;
}
}
Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]);
Operation newNode = nodes.AddBefore(node, argCopyOp);
Delete(nodes, node);
return newNode;
}
else
{
// TODO: Pass on stack.
return node;
}
}
private static void HandleReturnWindowsAbi(
CompilerContext cctx,
IntrusiveList<Operation> nodes,
Operand[] preservedArgs,
Operation node)
{
if (node.SourcesCount == 0)
{
return;
}
Operand source = node.GetSource(0);
Operand retReg;
if (source.Type.IsInteger())
{
retReg = Gpr(CallingConvention.GetIntReturnRegister(), source.Type);
}
else if (source.Type == OperandType.V128)
{
if (preservedArgs[0] == default)
{
Operand preservedArg = Local(OperandType.I64);
Operand arg0 = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
Operation copyOp = Operation(Instruction.Copy, preservedArg, arg0);
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
preservedArgs[0] = preservedArg;
}
retReg = preservedArgs[0];
}
else
{
retReg = Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
}
if (source.Type == OperandType.V128)
{
Operation retStoreOp = Operation(Instruction.Store, default, retReg, source);
nodes.AddBefore(node, retStoreOp);
}
else
{
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
nodes.AddBefore(node, retCopyOp);
}
node.SetSources(Array.Empty<Operand>());
}
private static void HandleReturnSystemVAbi(IntrusiveList<Operation> nodes, Operation node)
{
if (node.SourcesCount == 0)
{
return;
}
Operand source = node.GetSource(0);
if (source.Type == OperandType.V128)
{
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0)));
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1)));
}
else
{
Operand retReg = source.Type.IsInteger()
? Gpr(CallingConvention.GetIntReturnRegister(), source.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
nodes.AddBefore(node, retCopyOp);
}
}
private static Operand AddXmmCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
protected static Operand AddXmmCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
{
Operand temp = Local(source.Type);
Operand intConst = AddCopy(nodes, node, GetIntConst(source));
@ -1204,7 +592,7 @@ namespace ARMeilleure.CodeGen.X86
return temp;
}
private static Operand AddCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
protected static Operand AddCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
{
Operand temp = Local(source.Type);
@ -1229,7 +617,7 @@ namespace ARMeilleure.CodeGen.X86
return value;
}
private static void Delete(IntrusiveList<Operation> nodes, Operation node)
protected static void Delete(IntrusiveList<Operation> nodes, Operation node)
{
node.Destination = default;
@ -1241,12 +629,12 @@ namespace ARMeilleure.CodeGen.X86
nodes.Remove(node);
}
private static Operand Gpr(X86Register register, OperandType type)
protected static Operand Gpr(X86Register register, OperandType type)
{
return Register((int)register, RegisterType.Integer, type);
}
private static Operand Xmm(X86Register register, OperandType type)
protected static Operand Xmm(X86Register register, OperandType type)
{
return Register((int)register, RegisterType.Vector, type);
}

View File

@ -0,0 +1,334 @@
using ARMeilleure.CodeGen.RegisterAllocators;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
namespace ARMeilleure.CodeGen.X86
{
class PreAllocatorSystemV : PreAllocator
{
public static void InsertCallCopies(IntrusiveList<Operation> nodes, Operation node)
{
Operand dest = node.Destination;
List<Operand> sources = new List<Operand>
{
node.GetSource(0)
};
int argsCount = node.SourcesCount - 1;
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
int intCount = 0;
int vecCount = 0;
int stackOffset = 0;
for (int index = 0; index < argsCount; index++)
{
Operand source = node.GetSource(index + 1);
bool passOnReg;
if (source.Type.IsInteger())
{
passOnReg = intCount < intMax;
}
else if (source.Type == OperandType.V128)
{
passOnReg = intCount + 1 < intMax;
}
else
{
passOnReg = vecCount < vecMax;
}
if (source.Type == OperandType.V128 && passOnReg)
{
// V128 is a struct, we pass each half on a GPR if possible.
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
continue;
}
if (passOnReg)
{
Operand argReg = source.Type.IsInteger()
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
Operation copyOp = Operation(Instruction.Copy, argReg, source);
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
sources.Add(argReg);
}
else
{
Operand offset = Const(stackOffset);
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp));
stackOffset += source.Type.GetSizeInBytes();
}
}
node.SetSources(sources.ToArray());
if (dest != default)
{
if (dest.Type == OperandType.V128)
{
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
Operation operation = node;
node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg));
nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1)));
operation.Destination = default;
}
else
{
Operand retReg = dest.Type.IsInteger()
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
nodes.AddAfter(node, copyOp);
node.Destination = retReg;
}
}
}
public static void InsertTailcallCopies(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
{
List<Operand> sources = new List<Operand>
{
node.GetSource(0)
};
int argsCount = node.SourcesCount - 1;
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
int intCount = 0;
int vecCount = 0;
// Handle arguments passed on registers.
for (int index = 0; index < argsCount; index++)
{
Operand source = node.GetSource(1 + index);
bool passOnReg;
if (source.Type.IsInteger())
{
passOnReg = intCount + 1 < intMax;
}
else
{
passOnReg = vecCount < vecMax;
}
if (source.Type == OperandType.V128 && passOnReg)
{
// V128 is a struct, we pass each half on a GPR if possible.
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
continue;
}
if (passOnReg)
{
Operand argReg = source.Type.IsInteger()
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
Operation copyOp = Operation(Instruction.Copy, argReg, source);
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
sources.Add(argReg);
}
else
{
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
}
}
// The target address must be on the return registers, since we
// don't return anything and it is guaranteed to not be a
// callee saved register (which would be trashed on the epilogue).
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
nodes.AddBefore(node, addrCopyOp);
sources[0] = retReg;
node.SetSources(sources.ToArray());
}
public static Operation InsertLoadArgumentCopy(
CompilerContext cctx,
ref Span<Operation> buffer,
IntrusiveList<Operation> nodes,
Operand[] preservedArgs,
Operation node)
{
Operand source = node.GetSource(0);
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
int index = source.AsInt32();
int intCount = 0;
int vecCount = 0;
for (int cIndex = 0; cIndex < index; cIndex++)
{
OperandType argType = cctx.FuncArgTypes[cIndex];
if (argType.IsInteger())
{
intCount++;
}
else if (argType == OperandType.V128)
{
intCount += 2;
}
else
{
vecCount++;
}
}
bool passOnReg;
if (source.Type.IsInteger())
{
passOnReg = intCount < CallingConvention.GetIntArgumentsOnRegsCount();
}
else if (source.Type == OperandType.V128)
{
passOnReg = intCount + 1 < CallingConvention.GetIntArgumentsOnRegsCount();
}
else
{
passOnReg = vecCount < CallingConvention.GetVecArgumentsOnRegsCount();
}
if (passOnReg)
{
Operand dest = node.Destination;
if (preservedArgs[index] == default)
{
if (dest.Type == OperandType.V128)
{
// V128 is a struct, we pass each half on a GPR if possible.
Operand pArg = Local(OperandType.V128);
Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64);
Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64);
Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg);
Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1));
cctx.Cfg.Entry.Operations.AddFirst(copyH);
cctx.Cfg.Entry.Operations.AddFirst(copyL);
preservedArgs[index] = pArg;
}
else
{
Operand pArg = Local(dest.Type);
Operand argReg = dest.Type.IsInteger()
? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type);
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
preservedArgs[index] = pArg;
}
}
Operation nextNode;
if (dest.AssignmentsCount == 1)
{
// Let's propagate the argument if we can to avoid copies.
PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]);
nextNode = node.ListNext;
}
else
{
Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]);
nextNode = nodes.AddBefore(node, argCopyOp);
}
Delete(nodes, node);
return nextNode;
}
else
{
// TODO: Pass on stack.
return node;
}
}
public static void InsertReturnCopy(IntrusiveList<Operation> nodes, Operation node)
{
if (node.SourcesCount == 0)
{
return;
}
Operand source = node.GetSource(0);
if (source.Type == OperandType.V128)
{
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0)));
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1)));
}
else
{
Operand retReg = source.Type.IsInteger()
? Gpr(CallingConvention.GetIntReturnRegister(), source.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
nodes.AddBefore(node, retCopyOp);
}
}
}
}

View File

@ -0,0 +1,327 @@
using ARMeilleure.CodeGen.RegisterAllocators;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation;
using System;
using System.Diagnostics;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
namespace ARMeilleure.CodeGen.X86
{
class PreAllocatorWindows : PreAllocator
{
public static void InsertCallCopies(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
{
Operand dest = node.Destination;
// Handle struct arguments.
int retArgs = 0;
int stackAllocOffset = 0;
int AllocateOnStack(int size)
{
// We assume that the stack allocator is initially empty (TotalSize = 0).
// Taking that into account, we can reuse the space allocated for other
// calls by keeping track of our own allocated size (stackAllocOffset).
// If the space allocated is not big enough, then we just expand it.
int offset = stackAllocOffset;
if (stackAllocOffset + size > stackAlloc.TotalSize)
{
stackAlloc.Allocate((stackAllocOffset + size) - stackAlloc.TotalSize);
}
stackAllocOffset += size;
return offset;
}
Operand arg0Reg = default;
if (dest != default && dest.Type == OperandType.V128)
{
int stackOffset = AllocateOnStack(dest.Type.GetSizeInBytes());
arg0Reg = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
Operation allocOp = Operation(Instruction.StackAlloc, arg0Reg, Const(stackOffset));
nodes.AddBefore(node, allocOp);
retArgs = 1;
}
int argsCount = node.SourcesCount - 1;
int maxArgs = CallingConvention.GetArgumentsOnRegsCount() - retArgs;
if (argsCount > maxArgs)
{
argsCount = maxArgs;
}
Operand[] sources = new Operand[1 + retArgs + argsCount];
sources[0] = node.GetSource(0);
if (arg0Reg != default)
{
sources[1] = arg0Reg;
}
for (int index = 1; index < node.SourcesCount; index++)
{
Operand source = node.GetSource(index);
if (source.Type == OperandType.V128)
{
Operand stackAddr = Local(OperandType.I64);
int stackOffset = AllocateOnStack(source.Type.GetSizeInBytes());
nodes.AddBefore(node, Operation(Instruction.StackAlloc, stackAddr, Const(stackOffset)));
Operation storeOp = Operation(Instruction.Store, default, stackAddr, source);
InsertConstantRegCopies(nodes, nodes.AddBefore(node, storeOp));
node.SetSource(index, stackAddr);
}
}
// Handle arguments passed on registers.
for (int index = 0; index < argsCount; index++)
{
Operand source = node.GetSource(index + 1);
Operand argReg;
int argIndex = index + retArgs;
if (source.Type.IsInteger())
{
argReg = Gpr(CallingConvention.GetIntArgumentRegister(argIndex), source.Type);
}
else
{
argReg = Xmm(CallingConvention.GetVecArgumentRegister(argIndex), source.Type);
}
Operation copyOp = Operation(Instruction.Copy, argReg, source);
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
sources[1 + retArgs + index] = argReg;
}
// The remaining arguments (those that are not passed on registers)
// should be passed on the stack, we write them to the stack with "SpillArg".
for (int index = argsCount; index < node.SourcesCount - 1; index++)
{
Operand source = node.GetSource(index + 1);
Operand offset = Const((index + retArgs) * 8);
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp));
}
if (dest != default)
{
if (dest.Type == OperandType.V128)
{
Operand retValueAddr = Local(OperandType.I64);
nodes.AddBefore(node, Operation(Instruction.Copy, retValueAddr, arg0Reg));
Operation loadOp = Operation(Instruction.Load, dest, retValueAddr);
nodes.AddAfter(node, loadOp);
node.Destination = default;
}
else
{
Operand retReg = dest.Type.IsInteger()
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
nodes.AddAfter(node, copyOp);
node.Destination = retReg;
}
}
node.SetSources(sources);
}
public static void InsertTailcallCopies(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
{
int argsCount = node.SourcesCount - 1;
int maxArgs = CallingConvention.GetArgumentsOnRegsCount();
if (argsCount > maxArgs)
{
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
}
Operand[] sources = new Operand[1 + argsCount];
// Handle arguments passed on registers.
for (int index = 0; index < argsCount; index++)
{
Operand source = node.GetSource(1 + index);
Operand argReg = source.Type.IsInteger()
? Gpr(CallingConvention.GetIntArgumentRegister(index), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(index), source.Type);
Operation copyOp = Operation(Instruction.Copy, argReg, source);
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
sources[1 + index] = argReg;
}
// The target address must be on the return registers, since we
// don't return anything and it is guaranteed to not be a
// callee saved register (which would be trashed on the epilogue).
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
nodes.AddBefore(node, addrCopyOp);
sources[0] = retReg;
node.SetSources(sources);
}
public static Operation InsertLoadArgumentCopy(
CompilerContext cctx,
ref Span<Operation> buffer,
IntrusiveList<Operation> nodes,
Operand[] preservedArgs,
Operation node)
{
Operand source = node.GetSource(0);
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
int retArgs = cctx.FuncReturnType == OperandType.V128 ? 1 : 0;
int index = source.AsInt32() + retArgs;
if (index < CallingConvention.GetArgumentsOnRegsCount())
{
Operand dest = node.Destination;
if (preservedArgs[index] == default)
{
Operand argReg, pArg;
if (dest.Type.IsInteger())
{
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), dest.Type);
pArg = Local(dest.Type);
}
else if (dest.Type == OperandType.V128)
{
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), OperandType.I64);
pArg = Local(OperandType.I64);
}
else
{
argReg = Xmm(CallingConvention.GetVecArgumentRegister(index), dest.Type);
pArg = Local(dest.Type);
}
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
preservedArgs[index] = pArg;
}
Operation nextNode;
if (dest.Type != OperandType.V128 && dest.AssignmentsCount == 1)
{
// Let's propagate the argument if we can to avoid copies.
PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]);
nextNode = node.ListNext;
}
else
{
Operation argCopyOp = Operation(dest.Type == OperandType.V128
? Instruction.Load
: Instruction.Copy, dest, preservedArgs[index]);
nextNode = nodes.AddBefore(node, argCopyOp);
}
Delete(nodes, node);
return nextNode;
}
else
{
// TODO: Pass on stack.
return node;
}
}
public static void InsertReturnCopy(
CompilerContext cctx,
IntrusiveList<Operation> nodes,
Operand[] preservedArgs,
Operation node)
{
if (node.SourcesCount == 0)
{
return;
}
Operand source = node.GetSource(0);
Operand retReg;
if (source.Type.IsInteger())
{
retReg = Gpr(CallingConvention.GetIntReturnRegister(), source.Type);
}
else if (source.Type == OperandType.V128)
{
if (preservedArgs[0] == default)
{
Operand preservedArg = Local(OperandType.I64);
Operand arg0 = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
Operation copyOp = Operation(Instruction.Copy, preservedArg, arg0);
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
preservedArgs[0] = preservedArg;
}
retReg = preservedArgs[0];
}
else
{
retReg = Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
}
if (source.Type == OperandType.V128)
{
Operation retStoreOp = Operation(Instruction.Store, default, retReg, source);
nodes.AddBefore(node, retStoreOp);
}
else
{
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
nodes.AddBefore(node, retCopyOp);
}
node.SetSources(Array.Empty<Operand>());
}
}
}

View File

@ -219,6 +219,7 @@ namespace ARMeilleure.CodeGen.X86
Vfnmsub231sd,
Vfnmsub231ss,
Vpblendvb,
Vpternlogd,
Xor,
Xorpd,
Xorps,

View File

@ -17,7 +17,7 @@ namespace ARMeilleure.Decoders
{
uint[] tbl = new uint[256];
for (int idx = 0; idx < 256; idx++)
for (int idx = 0; idx < tbl.Length; idx++)
{
tbl[idx] = ExpandImm8ToFP32((uint)idx);
}
@ -29,7 +29,7 @@ namespace ARMeilleure.Decoders
{
ulong[] tbl = new ulong[256];
for (int idx = 0; idx < 256; idx++)
for (int idx = 0; idx < tbl.Length; idx++)
{
tbl[idx] = ExpandImm8ToFP64((ulong)idx);
}

View File

@ -1301,7 +1301,7 @@ namespace ARMeilleure.Decoders
{
List<InstInfo>[] temp = new List<InstInfo>[FastLookupSize];
for (int index = 0; index < FastLookupSize; index++)
for (int index = 0; index < temp.Length; index++)
{
temp[index] = new List<InstInfo>();
}
@ -1311,7 +1311,7 @@ namespace ARMeilleure.Decoders
int mask = ToFastLookupIndex(inst.Mask);
int value = ToFastLookupIndex(inst.Value);
for (int index = 0; index < FastLookupSize; index++)
for (int index = 0; index < temp.Length; index++)
{
if ((index & mask) == value)
{
@ -1320,7 +1320,7 @@ namespace ARMeilleure.Decoders
}
}
for (int index = 0; index < FastLookupSize; index++)
for (int index = 0; index < temp.Length; index++)
{
table[index] = temp[index].ToArray();
}

View File

@ -1,9 +1,8 @@
using ARMeilleure.Decoders;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation;
using static ARMeilleure.Instructions.InstEmitHelper;
using static ARMeilleure.Instructions.InstEmitHashHelper;
using static ARMeilleure.Instructions.InstEmitHelper;
namespace ARMeilleure.Instructions
{

View File

@ -4,9 +4,8 @@ using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation;
using System;
using System.Diagnostics;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
using static ARMeilleure.Instructions.InstEmitSimdHelper;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
namespace ARMeilleure.Instructions
{

View File

@ -254,7 +254,22 @@ namespace ARMeilleure.Instructions
public static void Not_V(ArmEmitterContext context)
{
if (Optimizations.UseSse2)
if (Optimizations.UseAvx512Ortho)
{
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
Operand n = GetVec(op.Rn);
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, n, Const(~0b10101010));
if (op.RegisterSize == RegisterSize.Simd64)
{
res = context.VectorZeroUpper64(res);
}
context.Copy(GetVec(op.Rd), res);
}
else if (Optimizations.UseSse2)
{
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
@ -283,6 +298,22 @@ namespace ARMeilleure.Instructions
{
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV);
}
else if (Optimizations.UseAvx512Ortho)
{
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm);
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
if (op.RegisterSize == RegisterSize.Simd64)
{
res = context.VectorZeroUpper64(res);
}
context.Copy(GetVec(op.Rd), res);
}
else if (Optimizations.UseSse2)
{
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;

View File

@ -151,6 +151,13 @@ namespace ARMeilleure.Instructions
{
InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrnV | Intrinsic.Arm64V128, n, m));
}
else if (Optimizations.UseAvx512Ortho)
{
EmitVectorBinaryOpSimd32(context, (n, m) =>
{
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
});
}
else if (Optimizations.UseSse2)
{
Operand mask = context.VectorOne();

View File

@ -34,7 +34,14 @@ namespace ARMeilleure.Instructions
public static void Vmvn_I(ArmEmitterContext context)
{
if (Optimizations.UseSse2)
if (Optimizations.UseAvx512Ortho)
{
EmitVectorUnaryOpSimd32(context, (op1) =>
{
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, op1, op1, Const(0b01010101));
});
}
else if (Optimizations.UseSse2)
{
EmitVectorUnaryOpSimd32(context, (op1) =>
{

View File

@ -173,6 +173,7 @@ namespace ARMeilleure.IntermediateRepresentation
X86Vfnmadd231ss,
X86Vfnmsub231sd,
X86Vfnmsub231ss,
X86Vpternlogd,
X86Xorpd,
X86Xorps,

View File

@ -23,6 +23,10 @@ namespace ARMeilleure
public static bool UseSse42IfAvailable { get; set; } = true;
public static bool UsePopCntIfAvailable { get; set; } = true;
public static bool UseAvxIfAvailable { get; set; } = true;
public static bool UseAvx512FIfAvailable { get; set; } = true;
public static bool UseAvx512VlIfAvailable { get; set; } = true;
public static bool UseAvx512BwIfAvailable { get; set; } = true;
public static bool UseAvx512DqIfAvailable { get; set; } = true;
public static bool UseF16cIfAvailable { get; set; } = true;
public static bool UseFmaIfAvailable { get; set; } = true;
public static bool UseAesniIfAvailable { get; set; } = true;
@ -47,11 +51,18 @@ namespace ARMeilleure
internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse;
internal static bool UseAvx512Vl => UseAvx512VlIfAvailable && X86HardwareCapabilities.SupportsAvx512Vl && !ForceLegacySse;
internal static bool UseAvx512Bw => UseAvx512BwIfAvailable && X86HardwareCapabilities.SupportsAvx512Bw && !ForceLegacySse;
internal static bool UseAvx512Dq => UseAvx512DqIfAvailable && X86HardwareCapabilities.SupportsAvx512Dq && !ForceLegacySse;
internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c;
internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma;
internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni;
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq;
internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha;
internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni;
internal static bool UseAvx512Ortho => UseAvx512F && UseAvx512Vl;
internal static bool UseAvx512OrthoFloat => UseAvx512Ortho && UseAvx512Dq;
}
}

View File

@ -6,6 +6,7 @@ using ARMeilleure.Memory;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
@ -29,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 4328; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 4485; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
@ -150,10 +151,10 @@ namespace ARMeilleure.Translation.PTC
private void InitializeCarriers()
{
_infosStream = new MemoryStream();
_infosStream = MemoryStreamManager.Shared.GetStream();
_codesList = new List<byte[]>();
_relocsStream = new MemoryStream();
_unwindInfosStream = new MemoryStream();
_relocsStream = MemoryStreamManager.Shared.GetStream();
_unwindInfosStream = MemoryStreamManager.Shared.GetStream();
}
private void DisposeCarriers()
@ -968,6 +969,7 @@ namespace ARMeilleure.Translation.PTC
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap,
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2,
(ulong)Arm64HardwareCapabilities.MacOsFeatureInfo,
0,
0);
}
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
@ -976,11 +978,12 @@ namespace ARMeilleure.Translation.PTC
(ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
(ulong)X86HardwareCapabilities.FeatureInfo1Edx,
(ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx);
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx,
(ulong)X86HardwareCapabilities.Xcr0InfoEax);
}
else
{
return new FeatureInfo(0, 0, 0, 0);
return new FeatureInfo(0, 0, 0, 0, 0);
}
}
@ -1001,7 +1004,7 @@ namespace ARMeilleure.Translation.PTC
return osPlatform;
}
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 78*/)]
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)]
private struct OuterHeader
{
public ulong Magic;
@ -1033,8 +1036,8 @@ namespace ARMeilleure.Translation.PTC
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 32*/)]
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3);
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 40*/)]
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3, ulong FeatureInfo4);
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
private struct InnerHeader

View File

@ -1,6 +1,7 @@
using ARMeilleure.State;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
@ -182,7 +183,7 @@ namespace ARMeilleure.Translation.PTC
return false;
}
using (MemoryStream stream = new MemoryStream())
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
{
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
@ -274,7 +275,7 @@ namespace ARMeilleure.Translation.PTC
outerHeader.SetHeaderHash();
using (MemoryStream stream = new MemoryStream())
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
{
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);

View File

@ -3,38 +3,38 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="0.10.18" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
<PackageVersion Include="Avalonia.Desktop" Version="0.10.18" />
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
<PackageVersion Include="Avalonia" Version="0.10.19" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="Crc32.NET" Version="1.2.0" />
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
<PackageVersion Include="DynamicData" Version="7.12.11" />
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.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.2.0" />
<PackageVersion Include="LibHac" Version="0.17.0" />
<PackageVersion Include="LibHac" Version="0.18.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageVersion Include="OpenTK.Core" Version="4.7.5" />
<PackageVersion Include="OpenTK.Graphics" Version="4.7.5" />
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.5" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" />
<PackageVersion Include="OpenTK.Core" Version="4.7.7" />
<PackageVersion Include="OpenTK.Graphics" Version="4.7.7" />
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.7" />
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
<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.26.1-build23" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.3-build25" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
@ -45,11 +45,9 @@
<PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
<PackageVersion Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
<PackageVersion Include="System.Management" Version="7.0.0" />
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
<PackageVersion Include="System.Threading.ThreadPool" Version="4.3.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-a913199" />
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.6.1" />
</ItemGroup>
</Project>

View File

@ -96,7 +96,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
- **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 four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. 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**

View File

@ -18,6 +18,7 @@ namespace Ryujinx.Audio.Backends.SDL2
private ulong _playedSampleCount;
private ManualResetEvent _updateRequiredEvent;
private uint _outputStream;
private bool _hasSetupError;
private SDL_AudioCallback _callbackDelegate;
private int _bytesPerFrame;
private uint _sampleCount;
@ -42,7 +43,7 @@ namespace Ryujinx.Audio.Backends.SDL2
private void EnsureAudioStreamSetup(AudioBuffer buffer)
{
uint bufferSampleCount = (uint)GetSampleCount(buffer);
bool needAudioSetup = _outputStream == 0 ||
bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) ||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
if (needAudioSetup)
@ -51,12 +52,9 @@ namespace Ryujinx.Audio.Backends.SDL2
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
if (newOutputStream == 0)
{
// No stream in place, this is unexpected.
throw new InvalidOperationException($"OpenStream failed with error: \"{SDL_GetError()}\"");
}
else
_hasSetupError = newOutputStream == 0;
if (!_hasSetupError)
{
if (_outputStream != 0)
{
@ -151,11 +149,20 @@ namespace Ryujinx.Audio.Backends.SDL2
{
EnsureAudioStreamSetup(buffer);
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
if (_outputStream != 0)
{
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
_queuedBuffers.Enqueue(driverBuffer);
_queuedBuffers.Enqueue(driverBuffer);
}
else
{
Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer));
_updateRequiredEvent.Set();
}
}
public override void SetVolume(float volume)

View File

@ -5,9 +5,8 @@ using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
using System.Threading;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
namespace Ryujinx.Audio.Backends.SoundIo
{

View File

@ -400,7 +400,9 @@ namespace Ryujinx.Audio.Common
{
uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax;
for (int i = 0; i < GetTotalBufferCount(); i++)
uint totalBufferCount = GetTotalBufferCount();
for (int i = 0; i < totalBufferCount; i++)
{
if (_buffers[bufferIndex].BufferTag == bufferTag)
{

View File

@ -1,8 +1,8 @@
using System;
using System.Diagnostics;
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
using System.Diagnostics;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{

View File

@ -1,7 +1,7 @@
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Server.MemoryPool;
using System;
using System.Diagnostics;

View File

@ -171,6 +171,11 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAntiAliasing;
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
_gpuCancellationTokenSource = new CancellationTokenSource();
}
@ -193,6 +198,17 @@ namespace Ryujinx.Ava
}
}
}
private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e)
{
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
}
private void UpdateScalingFilter(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.ScalingFilter> e)
{
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
}
private void ShowCursor()
{
@ -304,10 +320,14 @@ namespace Ryujinx.Ava
_viewModel.IsGameRunning = true;
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}";
string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}";
string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})";
string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
var activeProcess = Device.Processes.ActiveApplication;
var nacp = activeProcess.ApplicationControlProperties;
int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
Dispatcher.UIThread.InvokeAsync(() =>
{
@ -345,6 +365,11 @@ namespace Ryujinx.Ava
}
}
private void UpdateAntiAliasing(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.AntiAliasing> e)
{
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue);
}
private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e)
{
Device?.System.ChangeDockedModeState(e.NewValue);
@ -402,15 +427,18 @@ namespace Ryujinx.Ava
private void Dispose()
{
if (Device.Application != null)
if (Device.Processes != null)
{
_viewModel.UpdateGameMetadata(Device.Application.TitleIdText);
_viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
}
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter;
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel;
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing;
_topLevel.PointerMoved -= TopLevel_PointerMoved;
@ -515,7 +543,12 @@ namespace Ryujinx.Ava
{
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
Device.LoadNca(ApplicationPath);
if (!Device.LoadNca(ApplicationPath))
{
Device.Dispose();
return false;
}
}
else if (Directory.Exists(ApplicationPath))
{
@ -530,13 +563,23 @@ namespace Ryujinx.Ava
{
Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
Device.LoadCart(ApplicationPath, romFsFiles[0]);
if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
{
Device.Dispose();
return false;
}
}
else
{
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
Device.LoadCart(ApplicationPath);
if (!Device.LoadCart(ApplicationPath))
{
Device.Dispose();
return false;
}
}
}
else if (File.Exists(ApplicationPath))
@ -547,7 +590,12 @@ namespace Ryujinx.Ava
{
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
Device.LoadXci(ApplicationPath);
if (!Device.LoadXci(ApplicationPath))
{
Device.Dispose();
return false;
}
break;
}
@ -555,7 +603,12 @@ namespace Ryujinx.Ava
{
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
Device.LoadNca(ApplicationPath);
if (!Device.LoadNca(ApplicationPath))
{
Device.Dispose();
return false;
}
break;
}
@ -564,7 +617,12 @@ namespace Ryujinx.Ava
{
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
Device.LoadNsp(ApplicationPath);
if (!Device.LoadNsp(ApplicationPath))
{
Device.Dispose();
return false;
}
break;
}
@ -574,13 +632,18 @@ namespace Ryujinx.Ava
try
{
Device.LoadProgram(ApplicationPath);
if (!Device.LoadProgram(ApplicationPath))
{
Device.Dispose();
return false;
}
}
catch (ArgumentOutOfRangeException)
{
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
Dispose();
Device.Dispose();
return false;
}
@ -593,14 +656,14 @@ namespace Ryujinx.Ava
{
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
Dispose();
Device.Dispose();
return false;
}
DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName);
DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name);
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata =>
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
{
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
});
@ -788,6 +851,10 @@ namespace Ryujinx.Ava
Device.Gpu.Renderer.Initialize(_glLogLevel);
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value);
_renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
_renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
Width = (int)_rendererHost.Bounds.Width;
Height = (int)_rendererHost.Bounds.Height;
@ -922,7 +989,7 @@ namespace Ryujinx.Ava
{
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
{
Device.Application.DiskCacheLoadState?.Cancel();
Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
}
});
@ -1060,4 +1127,4 @@ namespace Ryujinx.Ava
return state;
}
}
}
}

View File

@ -583,10 +583,10 @@
"SelectUpdateDialogTitle": "Select update files",
"UserProfileWindowTitle": "User Profiles Manager",
"CheatWindowTitle": "Cheats Manager",
"DlcWindowTitle": "Downloadable Content Manager",
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
"UpdateWindowTitle": "Title Update Manager",
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
"DlcWindowHeading": "{0} Downloadable Content(s)",
"UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel",
"Save": "Save",
@ -626,6 +626,16 @@
"Recover": "Recover",
"UserProfilesRecoverHeading" : "Saves were found for the following accounts",
"UserProfilesRecoverEmptyList": "No profiles to recover",
"GraphicsAATooltip": "Applies anti-aliasing to the game render",
"GraphicsAALabel": "Anti-Aliasing:",
"GraphicsScalingFilterLabel": "Scaling Filter:",
"GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling",
"GraphicsScalingFilterLevelLabel": "Level",
"GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level",
"SmaaLow": "SMAA Low",
"SmaaMedium": "SMAA Medium",
"SmaaHigh": "SMAA High",
"SmaaUltra": "SMAA Ultra",
"UserEditorTitle" : "Edit User",
"UserEditorTitleCreate" : "Create User"
}

View File

@ -19,6 +19,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Buffers;
@ -193,7 +194,7 @@ namespace Ryujinx.Ava.Common
{
using var ncaFile = new UniqueRef<IFile>();
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
if (nca.Header.ContentType == NcaContentType.Program)
@ -227,7 +228,7 @@ namespace Ryujinx.Ava.Common
return;
}
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
if (updatePatchNca != null)
{
patchNca = updatePatchNca;
@ -249,8 +250,8 @@ namespace Ryujinx.Ava.Common
using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem);
using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination));
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref());
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref());
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref);
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref);
(Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token);

View File

@ -21,6 +21,7 @@ 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;
@ -57,7 +58,7 @@ namespace Ryujinx.Modules
// Detect current platform
if (OperatingSystem.IsMacOS())
{
_platformExt = "osx_x64.zip";
_platformExt = "macos_universal.app.tar.gz";
}
else if (OperatingSystem.IsWindows())
{
@ -286,22 +287,40 @@ namespace Ryujinx.Modules
if (_updateSuccessful)
{
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
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 ryuName = Path.GetFileName(Environment.ProcessPath);
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
string executablePath = Path.Combine(executableDirectory, ryuName);
if (!Path.Exists(ryuExe))
if (!Path.Exists(executablePath))
{
ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
executablePath = Path.Combine(executableDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
}
Process.Start(ryuExe, CommandLineState.Arguments);
// 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 = Process.GetCurrentProcess().Id.ToString();
executablePath = "/bin/bash";
arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
}
Process.Start(executablePath, arguments);
Environment.Exit(0);
}
}
@ -381,6 +400,15 @@ namespace Ryujinx.Modules
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);
@ -470,87 +498,98 @@ namespace Ryujinx.Modules
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 async void InstallUpdate(TaskDialog taskDialog, string updateFile)
{
// Extract Update
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
if (OperatingSystem.IsLinux())
await Task.Run(() =>
{
using Stream inStream = File.OpenRead(updateFile);
using GZipInputStream gzipStream = new(inStream);
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
await Task.Run(() =>
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
TarEntry tarEntry;
if (!OperatingSystem.IsWindows())
{
while ((tarEntry = tarStream.GetNextEntry()) is not null)
{
if (tarEntry.IsDirectory) continue;
string outPath = Path.Combine(UpdateDir, 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);
});
}
}
});
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
}
else
{
using Stream inStream = File.OpenRead(updateFile);
using ZipFile zipFile = new(inStream);
await Task.Run(() =>
ExtractTarGzipFile(taskDialog, updateFile, UpdateDir);
}
else if (OperatingSystem.IsWindows())
{
double count = 0;
foreach (ZipEntry zipEntry in zipFile)
{
count++;
if (zipEntry.IsDirectory) continue;
string outPath = Path.Combine(UpdateDir, 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);
});
}
});
}
ExtractZipFile(taskDialog, updateFile, UpdateDir);
}
else
{
throw new NotSupportedException();
}
});
// Delete downloaded zip
File.Delete(updateFile);
@ -560,38 +599,42 @@ namespace Ryujinx.Modules
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
// Replace old files
await Task.Run(() =>
// NOTE: On macOS, replacement is delayed to the restart phase.
if (!OperatingSystem.IsMacOS())
{
double count = 0;
foreach (string file in allFiles)
// Replace old files
await Task.Run(() =>
{
count++;
try
double count = 0;
foreach (string file in allFiles)
{
File.Move(file, file + ".ryuold");
Dispatcher.UIThread.Post(() =>
count++;
try
{
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
});
}
catch
{
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
}
}
File.Move(file, file + ".ryuold");
Dispatcher.UIThread.Post(() =>
{
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
Dispatcher.UIThread.Post(() =>
{
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
});
}
catch
{
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
}
}
Dispatcher.UIThread.Post(() =>
{
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
});
MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
});
MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
});
Directory.Delete(UpdateDir, true);
Directory.Delete(UpdateDir, true);
}
_updateSuccessful = true;
@ -601,7 +644,7 @@ namespace Ryujinx.Modules
public static bool CanUpdate(bool showWarnings)
{
#if !DISABLE_UPDATER
if (RuntimeInformation.OSArchitecture != Architecture.X64)
if (RuntimeInformation.OSArchitecture != Architecture.X64 && !OperatingSystem.IsMacOS())
{
if (showWarnings)
{
@ -674,7 +717,7 @@ namespace Ryujinx.Modules
#endif
}
// NOTE: This method should always reflect the latest build layout.s
// 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.

View File

@ -13,7 +13,7 @@
</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)" />
<Exec Command="codesign --entitlements '$(ProjectDir)..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
</Target>
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">

View File

@ -16,9 +16,9 @@ using Ryujinx.Ava.UI.Views.User;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
namespace Ryujinx.Ava.UI.Controls
@ -121,7 +121,7 @@ namespace Ryujinx.Ava.UI.Controls
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];

View File

@ -6,8 +6,8 @@ namespace Ryujinx.Ava.UI.Helpers
{
using AvaLogger = Avalonia.Logging.Logger;
using AvaLogLevel = Avalonia.Logging.LogEventLevel;
using RyuLogger = Ryujinx.Common.Logging.Logger;
using RyuLogClass = Ryujinx.Common.Logging.LogClass;
using RyuLogger = Ryujinx.Common.Logging.Logger;
internal class LoggerAdapter : Avalonia.Logging.ILogSink
{

View File

@ -1,4 +1,5 @@
using Ryujinx.Ava.UI.ViewModels;
using System.IO;
namespace Ryujinx.Ava.UI.Models
{
@ -21,6 +22,8 @@ namespace Ryujinx.Ava.UI.Models
public string ContainerPath { get; }
public string FullPath { get; }
public string FileName => Path.GetFileName(ContainerPath);
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
{
TitleId = titleId;

View File

@ -3,7 +3,6 @@ using Avalonia.Collections;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Windows;
@ -17,7 +16,6 @@ using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.ViewModels
@ -31,7 +29,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly byte[] _amiiboLogoBytes;
private readonly HttpClient _httpClient;
private readonly StyleableWindow _owner;
private Bitmap _amiiboImage;
private List<Amiibo.AmiiboApi> _amiiboList;
private AvaloniaList<Amiibo.AmiiboApi> _amiibos;

View File

@ -246,7 +246,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
using var file = new UniqueRef<IFile>();
romfs.OpenFile(ref file.Ref(), ("/" + item.FullPath).ToU8Span(), OpenMode.Read)
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read)
.ThrowIfFailure();
using (MemoryStream stream = new())

View File

@ -3,11 +3,8 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Svg.Skia;
using Avalonia.Threading;
using LibHac.Bcat;
using LibHac.Tools.Fs;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Windows;

View File

@ -0,0 +1,340 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading;
using DynamicData;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Path = System.IO.Path;
namespace Ryujinx.Ava.UI.ViewModels
{
public class DownloadableContentManagerViewModel : BaseModel
{
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
private readonly string _downloadableContentJsonPath;
private VirtualFileSystem _virtualFileSystem;
private AvaloniaList<DownloadableContentModel> _downloadableContents = new();
private AvaloniaList<DownloadableContentModel> _views = new();
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
private string _search;
private ulong _titleId;
private string _titleName;
public AvaloniaList<DownloadableContentModel> DownloadableContents
{
get => _downloadableContents;
set
{
_downloadableContents = value;
OnPropertyChanged();
OnPropertyChanged(nameof(UpdateCount));
Sort();
}
}
public AvaloniaList<DownloadableContentModel> Views
{
get => _views;
set
{
_views = value;
OnPropertyChanged();
}
}
public AvaloniaList<DownloadableContentModel> SelectedDownloadableContents
{
get => _selectedDownloadableContents;
set
{
_selectedDownloadableContents = value;
OnPropertyChanged();
}
}
public string Search
{
get => _search;
set
{
_search = value;
OnPropertyChanged();
Sort();
}
}
public string UpdateCount
{
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
}
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
_virtualFileSystem = virtualFileSystem;
_titleId = titleId;
_titleName = titleName;
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
try
{
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
}
catch
{
Logger.Error?.Print(LogClass.Configuration, "Downloadable Content JSON failed to deserialize.");
_downloadableContentContainerList = new List<DownloadableContentContainer>();
}
LoadDownloadableContents();
}
private void LoadDownloadableContents()
{
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
{
if (File.Exists(downloadableContentContainer.ContainerPath))
{
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
{
using UniqueRef<IFile> ncaFile = new();
partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
if (nca != null)
{
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath,
downloadableContentNca.Enabled);
DownloadableContents.Add(content);
if (content.Enabled)
{
SelectedDownloadableContents.Add(content);
}
OnPropertyChanged(nameof(UpdateCount));
}
}
}
}
// NOTE: Save the list again to remove leftovers.
Save();
Sort();
}
public void Sort()
{
DownloadableContents.AsObservableChangeSet()
.Filter(Filter)
.Bind(out var view).AsObservableList();
_views.Clear();
_views.AddRange(view);
OnPropertyChanged(nameof(Views));
}
private bool Filter(object arg)
{
if (arg is DownloadableContentModel content)
{
return string.IsNullOrWhiteSpace(_search) || content.FileName.ToLower().Contains(_search.ToLower()) || content.TitleId.ToLower().Contains(_search.ToLower());
}
return false;
}
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
{
try
{
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
}
catch (Exception ex)
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogLoadNcaErrorMessage], ex.Message, containerPath));
});
}
return null;
}
public async void Add()
{
OpenFileDialog dialog = new OpenFileDialog()
{
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
AllowMultiple = true
};
dialog.Filters.Add(new FileDialogFilter
{
Name = "NSP",
Extensions = { "nsp" }
});
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
string[] files = await dialog.ShowAsync(desktop.MainWindow);
if (files != null)
{
foreach (string file in files)
{
await AddDownloadableContent(file);
}
}
}
}
private async Task AddDownloadableContent(string path)
{
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
{
return;
}
using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
{
continue;
}
if (nca.Header.ContentType == NcaContentType.PublicData)
{
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
{
break;
}
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true);
DownloadableContents.Add(content);
SelectedDownloadableContents.Add(content);
OnPropertyChanged(nameof(UpdateCount));
Sort();
containsDownloadableContent = true;
}
}
if (!containsDownloadableContent)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
}
}
public void Remove(DownloadableContentModel model)
{
DownloadableContents.Remove(model);
OnPropertyChanged(nameof(UpdateCount));
Sort();
}
public void RemoveAll()
{
DownloadableContents.Clear();
OnPropertyChanged(nameof(UpdateCount));
Sort();
}
public void EnableAll()
{
SelectedDownloadableContents = new(DownloadableContents);
}
public void DisableAll()
{
SelectedDownloadableContents.Clear();
}
public void Save()
{
_downloadableContentContainerList.Clear();
DownloadableContentContainer container = default;
foreach (DownloadableContentModel downloadableContent in DownloadableContents)
{
if (container.ContainerPath != downloadableContent.ContainerPath)
{
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
{
_downloadableContentContainerList.Add(container);
}
container = new DownloadableContentContainer
{
ContainerPath = downloadableContent.ContainerPath,
DownloadableContentNcaList = new List<DownloadableContentNca>()
};
}
container.DownloadableContentNcaList.Add(new DownloadableContentNca
{
Enabled = downloadableContent.Enabled,
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
FullPath = downloadableContent.FullPath
});
}
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
{
_downloadableContentContainerList.Add(container);
}
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
{
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
}
}
}
}

View File

@ -7,8 +7,7 @@ using DynamicData;
using DynamicData.Binding;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
@ -1209,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public void SetUIProgressHandlers(Switch emulationContext)
{
if (emulationContext.Application.DiskCacheLoadState != null)
if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
{
emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
}
emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
@ -1565,7 +1564,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
if (SelectedApplication != null)
{
await new DownloadableContentManagerWindow(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName).ShowDialog(TopLevel as Window);
await DownloadableContentManagerWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName);
}
}
@ -1706,8 +1705,8 @@ namespace Ryujinx.Ava.UI.ViewModels
if (string.IsNullOrWhiteSpace(titleName))
{
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName);
TitleName = AppHost.Device.Application.TitleName;
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
}
SwitchToRenderer(startFullscreen);

View File

@ -45,6 +45,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private KeyboardHotkeys _keyboardHotkeys;
private int _graphicsBackendIndex;
private string _customThemePath;
private int _scalingFilter;
private int _scalingFilterLevel;
public event Action CloseWindow;
public event Action SaveSettingsEvent;
@ -153,6 +155,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsSDL2Enabled { get; set; }
public bool EnableCustomTheme { get; set; }
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
public bool UseHypervisor { get; set; }
@ -179,6 +183,18 @@ namespace Ryujinx.Ava.UI.ViewModels
public int AudioBackend { get; set; }
public int MaxAnisotropy { get; set; }
public int AspectRatio { get; set; }
public int AntiAliasingEffect { get; set; }
public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0");
public int ScalingFilterLevel
{
get => _scalingFilterLevel;
set
{
_scalingFilterLevel = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ScalingFilterLevelText));
}
}
public int OpenglDebugLevel { get; set; }
public int MemoryMode { get; set; }
public int BaseStyleIndex { get; set; }
@ -192,6 +208,16 @@ namespace Ryujinx.Ava.UI.ViewModels
OnPropertyChanged(nameof(IsVulkanSelected));
}
}
public int ScalingFilter
{
get => _scalingFilter;
set
{
_scalingFilter = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsScalingFilterActive));
}
}
public int PreferredGpuIndex { get; set; }
@ -210,7 +236,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public DateTimeOffset DateOffset { get; set; }
public TimeSpan TimeOffset { get; set; }
private AvaloniaList<TimeZone> TimeZones { get; set; }
internal AvaloniaList<TimeZone> TimeZones { get; set; }
public AvaloniaList<string> GameDirectories { get; set; }
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
@ -365,6 +391,9 @@ namespace Ryujinx.Ava.UI.ViewModels
AspectRatio = (int)config.Graphics.AspectRatio.Value;
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
ShaderDumpPath = config.Graphics.ShadersDumpPath;
AntiAliasingEffect = (int)config.Graphics.AntiAliasing.Value;
ScalingFilter = (int)config.Graphics.ScalingFilter.Value;
ScalingFilterLevel = config.Graphics.ScalingFilterLevel.Value;
// Audio
AudioBackend = (int)config.System.AudioBackend.Value;
@ -447,6 +476,9 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
config.Graphics.AntiAliasing.Value = (AntiAliasing)AntiAliasingEffect;
config.Graphics.ScalingFilter.Value = (ScalingFilter)ScalingFilter;
config.Graphics.ScalingFilterLevel.Value = ScalingFilterLevel;
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
{

View File

@ -17,6 +17,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Ui.App.Common;
using System;
using System.Collections.Generic;
using System.IO;
@ -162,7 +163,7 @@ public class TitleUpdateViewModel : BaseModel
try
{
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
if (controlNca != null && patchNca != null)
{
@ -170,7 +171,7 @@ public class TitleUpdateViewModel : BaseModel
using UniqueRef<IFile> nacpFile = new();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
TitleUpdates.Add(new TitleUpdateModel(controlData, path));

View File

@ -126,7 +126,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
using var file = new UniqueRef<IFile>();
romfs.OpenFile(ref file.Ref(), ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
using (MemoryStream stream = new())
using (MemoryStream streamPng = new())

View File

@ -1,8 +1,8 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
@ -126,7 +126,7 @@ namespace Ryujinx.Ava.UI.Views.Main
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{
string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper();
string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
await window.ShowDialog(Window);
@ -148,13 +148,11 @@ namespace Ryujinx.Ava.UI.Views.Main
return;
}
ApplicationLoader application = ViewModel.AppHost.Device.Application;
if (application != null)
{
await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
ViewModel.AppHost.Device.EnableCheats();
}
await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window);
ViewModel.AppHost.Device.EnableCheats();
}
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)

View File

@ -7,6 +7,7 @@
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
Design.Width="1000"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel">
@ -111,6 +112,83 @@
Minimum="0.1"
Value="{Binding CustomResolutionScale}" />
</StackPanel>
<StackPanel
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale GraphicsAATooltip}"
Text="{locale:Locale GraphicsAALabel}"
Width="250" />
<ComboBox Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale GraphicsAATooltip}"
SelectedIndex="{Binding AntiAliasingEffect}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="FXAA" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SmaaLow}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SmaaMedium}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SmaaHigh}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SmaaUltra}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
<StackPanel
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}"
Text="{locale:Locale GraphicsScalingFilterLabel}"
Width="250" />
<ComboBox Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}"
SelectedIndex="{Binding ScalingFilter}">
<ComboBoxItem>
<TextBlock Text="Bilinear" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="Nearest" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="FSR" />
</ComboBoxItem>
</ComboBox>
<Slider Value="{Binding ScalingFilterLevel}"
ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}"
MinWidth="150"
Margin="10,-3,0,0"
Height="32"
Padding="0,-5"
IsVisible="{Binding IsScalingFilterActive}"
TickFrequency="1"
IsSnapToTickEnabled="True"
LargeChange="10"
SmallChange="1"
VerticalAlignment="Center"
Minimum="0"
Maximum="100" />
<TextBlock Margin="5,0"
Width="40"
IsVisible="{Binding IsScalingFilterActive}"
Text="{Binding ScalingFilterLevelText}"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale AnisotropyTooltip}"

View File

@ -5,8 +5,8 @@ using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System;
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;

View File

@ -76,7 +76,7 @@ namespace Ryujinx.Ava.UI.Views.User
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];

View File

@ -1,172 +1,194 @@
<window:StyleableWindow
<UserControl
x:Class="Ryujinx.Ava.UI.Windows.DownloadableContentManagerWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
Width="800"
Height="500"
MinWidth="800"
MinHeight="500"
MaxWidth="800"
MaxHeight="500"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
Width="500"
Height="380"
mc:Ignorable="d"
x:CompileBindings="True"
x:DataType="viewModels:DownloadableContentManagerViewModel"
Focusable="True">
<Grid Name="DownloadableContentGrid" Margin="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Name="Heading"
Grid.Row="1"
MaxWidth="500"
Margin="20,15,20,20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
LineHeight="18"
TextAlignment="Center"
TextWrapping="Wrap" />
<DockPanel
Grid.Row="2"
Margin="0"
HorizontalAlignment="Left">
<Button
Name="EnableAllButton"
MinWidth="90"
Margin="5"
Command="{Binding EnableAll}">
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
</Button>
<Button
Name="DisableAllButton"
MinWidth="90"
Margin="5"
Command="{Binding DisableAll}">
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
</Button>
</DockPanel>
<Panel
Margin="0 0 0 10"
Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Text="{Binding UpdateCount}" />
<StackPanel
Margin="10 0"
Grid.Column="1"
Orientation="Horizontal">
<Button
Name="EnableAllButton"
MinWidth="90"
Margin="5"
Command="{ReflectionBinding EnableAll}">
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
</Button>
<Button
Name="DisableAllButton"
MinWidth="90"
Margin="5"
Command="{ReflectionBinding DisableAll}">
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
</Button>
</StackPanel>
<TextBox
Grid.Column="2"
MinHeight="27"
MaxHeight="27"
HorizontalAlignment="Stretch"
Watermark="{locale:Locale Search}"
Text="{Binding Search}" />
</Grid>
</Panel>
<Border
Grid.Row="3"
Margin="5"
Grid.Row="1"
Margin="0 0 0 24"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="Gray"
BorderThickness="1">
<ScrollViewer
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<DataGrid
Name="DlcDataGrid"
MinHeight="200"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CanUserReorderColumns="False"
CanUserResizeColumns="True"
CanUserSortColumns="True"
HorizontalScrollBarVisibility="Auto"
Items="{Binding _downloadableContents}"
SelectionMode="Extended"
VerticalScrollBarVisibility="Auto">
<DataGrid.Styles>
<Styles>
<Style Selector="DataGridCell:nth-child(3), DataGridCell:nth-child(4)">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</Styles>
<Styles>
<Style Selector="DataGridCell:nth-child(1)">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
</Style>
</Styles>
</DataGrid.Styles>
<DataGrid.Columns>
<DataGridTemplateColumn Width="90">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
Width="50"
MinWidth="40"
HorizontalAlignment="Center"
IsChecked="{Binding Enabled}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
</DataGridTemplateColumn.Header>
</DataGridTemplateColumn>
<DataGridTextColumn Width="140" Binding="{Binding TitleId}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="280" Binding="{Binding FullPath}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding ContainerPath}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1"
CornerRadius="5"
Padding="2.5">
<ListBox
AutoScrollToSelectedItem="False"
VirtualizationMode="None"
SelectionMode="Multiple, Toggle"
Background="Transparent"
SelectionChanged="OnSelectionChanged"
SelectedItems="{Binding SelectedDownloadableContents, Mode=TwoWay}"
Items="{Binding Views}">
<ListBox.DataTemplates>
<DataTemplate
DataType="models:DownloadableContentModel">
<Panel Margin="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid
Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
MaxLines="2"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
Text="{Binding FileName}" />
<TextBlock
Grid.Column="1"
Margin="10 0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding TitleId}" />
</Grid>
<StackPanel
Grid.Column="1"
Spacing="10"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button
VerticalAlignment="Center"
HorizontalAlignment="Right"
Padding="10"
MinWidth="0"
MinHeight="0"
Click="OpenLocation">
<ui:SymbolIcon
Symbol="OpenFolder"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Button>
<Button
VerticalAlignment="Center"
HorizontalAlignment="Right"
Padding="10"
MinWidth="0"
MinHeight="0"
Click="RemoveDLC">
<ui:SymbolIcon
Symbol="Cancel"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Button>
</StackPanel>
</Grid>
</Panel>
</DataTemplate>
</ListBox.DataTemplates>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Background" Value="Transparent" />
</Style>
</ListBox.Styles>
</ListBox>
</Border>
<DockPanel
Grid.Row="4"
Margin="0"
<Panel
Grid.Row="2"
HorizontalAlignment="Stretch">
<DockPanel Margin="0" HorizontalAlignment="Left">
<StackPanel
Orientation="Horizontal"
Spacing="10"
HorizontalAlignment="Left">
<Button
Name="AddButton"
MinWidth="90"
Margin="5"
Command="{Binding Add}">
Command="{ReflectionBinding Add}">
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
</Button>
<Button
Name="RemoveButton"
MinWidth="90"
Margin="5"
Command="{Binding RemoveSelected}">
<TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" />
</Button>
<Button
Name="RemoveAllButton"
MinWidth="90"
Margin="5"
Command="{Binding RemoveAll}">
Command="{ReflectionBinding RemoveAll}">
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
</Button>
</DockPanel>
<DockPanel Margin="0" HorizontalAlignment="Right">
</StackPanel>
<StackPanel
Orientation="Horizontal"
Spacing="10"
HorizontalAlignment="Right">
<Button
Name="SaveButton"
MinWidth="90"
Margin="5"
Command="{Binding SaveAndClose}">
Click="SaveAndClose">
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
</Button>
<Button
Name="CancelButton"
MinWidth="90"
Margin="5"
Command="{Binding Close}">
Click="Close">
<TextBlock Text="{locale:Locale InputDialogCancel}" />
</Button>
</DockPanel>
</DockPanel>
</StackPanel>
</Panel>
</Grid>
</window:StyleableWindow>
</UserControl>

View File

@ -1,314 +1,115 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Threading;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Avalonia.Interactivity;
using Avalonia.Styling;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.HLE.FileSystem;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using Ryujinx.Ui.Common.Helper;
using System.Threading.Tasks;
using Path = System.IO.Path;
using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Windows
{
public partial class DownloadableContentManagerWindow : StyleableWindow
public partial class DownloadableContentManagerWindow : UserControl
{
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
private readonly string _downloadableContentJsonPath;
private VirtualFileSystem _virtualFileSystem { get; }
private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; }
private ulong _titleId { get; }
private string _titleName { get; }
public DownloadableContentManagerViewModel ViewModel;
public DownloadableContentManagerWindow()
{
DataContext = this;
InitializeComponent();
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.DlcWindowTitle]} - {_titleName} ({_titleId:X16})";
}
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
_virtualFileSystem = virtualFileSystem;
_downloadableContents = new AvaloniaList<DownloadableContentModel>();
_titleId = titleId;
_titleName = titleName;
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
try
{
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
}
catch
{
_downloadableContentContainerList = new List<DownloadableContentContainer>();
}
DataContext = this;
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId, titleName);
InitializeComponent();
RemoveButton.IsEnabled = false;
DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged;
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.DlcWindowTitle]} - {_titleName} ({_titleId:X16})";
LoadDownloadableContents();
PrintHeading();
}
private void DlcDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
RemoveButton.IsEnabled = (DlcDataGrid.SelectedItems.Count > 0);
}
private void PrintHeading()
{
Heading.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DlcWindowHeading, _downloadableContents.Count, _titleName, _titleId.ToString("X16"));
}
private void LoadDownloadableContents()
{
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
ContentDialog contentDialog = new()
{
if (File.Exists(downloadableContentContainer.ContainerPath))
{
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
{
using UniqueRef<IFile> ncaFile = new();
partitionFileSystem.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
if (nca != null)
{
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath,
downloadableContentNca.Enabled));
}
}
}
}
// NOTE: Save the list again to remove leftovers.
Save();
}
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
{
try
{
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
}
catch (Exception ex)
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, containerPath));
});
}
return null;
}
private async Task AddDownloadableContent(string path)
{
if (!File.Exists(path) || _downloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
{
return;
}
using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
partitionFileSystem.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
{
continue;
}
if (nca.Header.ContentType == NcaContentType.PublicData)
{
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
{
break;
}
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
containsDownloadableContent = true;
}
}
if (!containsDownloadableContent)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
}
}
private void RemoveDownloadableContents(bool removeSelectedOnly = false)
{
if (removeSelectedOnly)
{
AvaloniaList<DownloadableContentModel> removedItems = new();
foreach (var item in DlcDataGrid.SelectedItems)
{
removedItems.Add(item as DownloadableContentModel);
}
DlcDataGrid.SelectedItems.Clear();
foreach (var item in removedItems)
{
_downloadableContents.RemoveAll(_downloadableContents.Where(x => x.TitleId == item.TitleId).ToList());
}
}
else
{
_downloadableContents.Clear();
}
PrintHeading();
}
public void RemoveSelected()
{
RemoveDownloadableContents(true);
}
public void RemoveAll()
{
RemoveDownloadableContents();
}
public void EnableAll()
{
foreach(var item in _downloadableContents)
{
item.Enabled = true;
}
}
public void DisableAll()
{
foreach (var item in _downloadableContents)
{
item.Enabled = false;
}
}
public async void Add()
{
OpenFileDialog dialog = new OpenFileDialog()
{
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
AllowMultiple = true
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = "",
Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId, titleName),
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16"))
};
dialog.Filters.Add(new FileDialogFilter
{
Name = "NSP",
Extensions = { "nsp" }
});
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false));
string[] files = await dialog.ShowAsync(this);
contentDialog.Styles.Add(bottomBorder);
if (files != null)
{
foreach (string file in files)
{
await AddDownloadableContent(file);
}
}
PrintHeading();
await ContentDialogHelper.ShowAsync(contentDialog);
}
public void Save()
private void SaveAndClose(object sender, RoutedEventArgs routedEventArgs)
{
_downloadableContentContainerList.Clear();
ViewModel.Save();
((ContentDialog)Parent).Hide();
}
DownloadableContentContainer container = default;
private void Close(object sender, RoutedEventArgs e)
{
((ContentDialog)Parent).Hide();
}
foreach (DownloadableContentModel downloadableContent in _downloadableContents)
private void RemoveDLC(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{
if (container.ContainerPath != downloadableContent.ContainerPath)
if (button.DataContext is DownloadableContentModel model)
{
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
ViewModel.Remove(model);
}
}
}
private void OpenLocation(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{
if (button.DataContext is DownloadableContentModel model)
{
OpenHelper.LocateFile(model.ContainerPath);
}
}
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var content in e.AddedItems)
{
if (content is DownloadableContentModel model)
{
var index = ViewModel.DownloadableContents.IndexOf(model);
if (index != -1)
{
_downloadableContentContainerList.Add(container);
ViewModel.DownloadableContents[index].Enabled = true;
}
container = new DownloadableContentContainer
{
ContainerPath = downloadableContent.ContainerPath,
DownloadableContentNcaList = new List<DownloadableContentNca>()
};
}
}
container.DownloadableContentNcaList.Add(new DownloadableContentNca
foreach (var content in e.RemovedItems)
{
if (content is DownloadableContentModel model)
{
Enabled = downloadableContent.Enabled,
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
FullPath = downloadableContent.FullPath
});
}
var index = ViewModel.DownloadableContents.IndexOf(model);
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
{
_downloadableContentContainerList.Add(container);
if (index != -1)
{
ViewModel.DownloadableContents[index].Enabled = false;
}
}
}
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
{
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
}
}
public void SaveAndClose()
{
Save();
Close();
}
}
}

View File

@ -125,7 +125,7 @@ namespace Ryujinx.Ava.UI.Windows
public static Bgra32[] GetBuffer(Image<Bgra32> image)
{
return image.TryGetSinglePixelSpan(out var data) ? data.ToArray() : new Bgra32[0];
return image.TryGetSinglePixelSpan(out var data) ? data.ToArray() : Array.Empty<Bgra32>();
}
private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color)

View File

@ -0,0 +1,12 @@
namespace Ryujinx.Common.Configuration
{
public enum AntiAliasing
{
None,
Fxaa,
SmaaLow,
SmaaMedium,
SmaaHigh,
SmaaUltra
}
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.Common.Configuration
{
public enum ScalingFilter
{
Bilinear,
Nearest,
Fsr
}
}

View File

@ -12,19 +12,5 @@ namespace Ryujinx.Common
{
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
}
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
where T : unmanaged
{
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
writer.Write(data);
}
public static void Write(this BinaryWriter writer, UInt128 value)
{
writer.Write((ulong)value);
writer.Write((ulong)(value >> 64));
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Ryujinx.Common
{
public static class BinaryWriterExtensions
{
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
where T : unmanaged
{
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
writer.Write(data);
}
public static void Write(this BinaryWriter writer, UInt128 value)
{
writer.Write((ulong)value);
writer.Write((ulong)(value >> 64));
}
public static void Write(this BinaryWriter writer, MemoryStream stream)
{
stream.CopyTo(writer.BaseStream);
}
}
}

View File

@ -0,0 +1,138 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.InteropServices;
namespace Ryujinx.Common
{
public static class StreamExtensions
{
/// <summary>
/// Writes a <cref="ReadOnlySpan<int>" /> to this stream.
///
/// This default implementation converts each buffer value to a stack-allocated
/// byte array, then writes it to the Stream using <cref="System.Stream.Write(byte[])" />.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="buffer">The buffer of values to be written</param>
public static void Write(this Stream stream, ReadOnlySpan<int> buffer)
{
if (buffer.Length == 0)
{
return;
}
if (BitConverter.IsLittleEndian)
{
ReadOnlySpan<byte> byteBuffer = MemoryMarshal.Cast<int, byte>(buffer);
stream.Write(byteBuffer);
}
else
{
Span<byte> byteBuffer = stackalloc byte[sizeof(int)];
foreach (int value in buffer)
{
BinaryPrimitives.WriteInt32LittleEndian(byteBuffer, value);
stream.Write(byteBuffer);
}
}
}
/// <summary>
/// Writes a four-byte signed integer to this stream. The current position
/// of the stream is advanced by four.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, int value)
{
Span<byte> buffer = stackalloc byte[sizeof(int)];
BinaryPrimitives.WriteInt32LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
/// Writes an eight-byte signed integer to this stream. The current position
/// of the stream is advanced by eight.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, long value)
{
Span<byte> buffer = stackalloc byte[sizeof(long)];
BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
// Writes a four-byte unsigned integer to this stream. The current position
// of the stream is advanced by four.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
/// Writes an eight-byte unsigned integer to this stream. The current
/// position of the stream is advanced by eight.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="value">The value to be written</param>
public static void Write(this Stream stream, ulong value)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
stream.Write(buffer);
}
/// <summary>
/// Writes the contents of source to stream by calling source.CopyTo(stream).
/// Provides consistency with other Stream.Write methods.
/// </summary>
/// <param name="stream">The stream to be written to</param>
/// <param name="source">The stream to be read from</param>
public static void Write(this Stream stream, Stream source)
{
source.CopyTo(stream);
}
/// <summary>
/// Writes a sequence of bytes to the Stream.
/// </summary>
/// <param name="stream">The stream to be written to.</param>
/// <param name="value">The byte to be written</param>
/// <param name="count">The number of times the value should be written</param>
public static void WriteByte(this Stream stream, byte value, int count)
{
if (count <= 0)
{
return;
}
const int BlockSize = 16;
int blockCount = count / BlockSize;
if (blockCount > 0)
{
Span<byte> span = stackalloc byte[BlockSize];
span.Fill(value);
for (int x = 0; x < blockCount; x++)
{
stream.Write(span);
}
}
int nonBlockBytes = count % BlockSize;
for (int x = 0; x < nonBlockBytes; x++)
{
stream.WriteByte(value);
}
}
}
}

View File

@ -1,3 +1,4 @@
using Ryujinx.Common.SystemInterop;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -14,6 +15,8 @@ namespace Ryujinx.Common.Logging
private static readonly List<ILogTarget> m_LogTargets;
private static readonly StdErrAdapter _stdErrAdapter;
public static event EventHandler<LogEventArgs> Updated;
public readonly struct Log
@ -77,7 +80,13 @@ namespace Ryujinx.Common.Logging
{
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message), data));
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrintRawMsg(string message)
{
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, message));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string FormatMessage(LogClass Class, string Caller, string Message) => $"{Class} {Caller}: {Message}";
@ -119,6 +128,8 @@ namespace Ryujinx.Common.Logging
Warning = new Log(LogLevel.Warning);
Info = new Log(LogLevel.Info);
Trace = new Log(LogLevel.Trace);
_stdErrAdapter = new StdErrAdapter();
}
public static void RestartTime()
@ -164,6 +175,8 @@ namespace Ryujinx.Common.Logging
{
Updated = null;
_stdErrAdapter.Dispose();
foreach (var target in m_LogTargets)
{
target.Dispose();

View File

@ -0,0 +1,99 @@
using Microsoft.IO;
using System;
namespace Ryujinx.Common.Memory
{
public static class MemoryStreamManager
{
private static readonly RecyclableMemoryStreamManager _shared = new RecyclableMemoryStreamManager();
/// <summary>
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
/// and b) return them as <c>RecyclableMemoryStream</c> so we don't have to cast.
/// </summary>
public static class Shared
{
/// <summary>
/// Retrieve a new <c>MemoryStream</c> object with no tag and a default initial capacity.
/// </summary>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream()
=> new RecyclableMemoryStream(_shared);
/// <summary>
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(byte[] buffer)
=> GetStream(Guid.NewGuid(), null, buffer, 0, buffer.Length);
/// <summary>
/// Retrieve a new <c>MemoryStream</c> object with the given tag and with contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(ReadOnlySpan<byte> buffer)
=> GetStream(Guid.NewGuid(), null, buffer);
/// <summary>
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
/// <param name="tag">A tag which can be used to track the source of the stream</param>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(Guid id, string tag, ReadOnlySpan<byte> buffer)
{
RecyclableMemoryStream stream = null;
try
{
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
stream.Write(buffer);
stream.Position = 0;
return stream;
}
catch
{
stream?.Dispose();
throw;
}
}
/// <summary>
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
/// buffer. The provided buffer is not wrapped or used after construction.
/// </summary>
/// <remarks>The new stream's position is set to the beginning of the stream when returned</remarks>
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
/// <param name="tag">A tag which can be used to track the source of the stream</param>
/// <param name="buffer">The byte buffer to copy data from</param>
/// <param name="offset">The offset from the start of the buffer to copy from</param>
/// <param name="count">The number of bytes to copy from the buffer</param>
/// <returns>A <c>RecyclableMemoryStream</c></returns>
public static RecyclableMemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count)
{
RecyclableMemoryStream stream = null;
try
{
stream = new RecyclableMemoryStream(_shared, id, tag, count);
stream.Write(buffer, offset, count);
stream.Position = 0;
return stream;
}
catch
{
stream?.Dispose();
throw;
}
}
}
}
}

View File

@ -3,9 +3,11 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
<PackageReference Include="MsgPack.Cli" />
<PackageReference Include="System.Management" />
</ItemGroup>

View File

@ -1,9 +1,9 @@
using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.Versioning;
using Ryujinx.Common.Logging;
namespace Ryujinx.Common.SystemInfo
{

View File

@ -1,9 +1,9 @@
using Ryujinx.Common.Logging;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using Ryujinx.Common.Logging;
namespace Ryujinx.Common.SystemInfo
{

View File

@ -1,8 +1,8 @@
using System;
using Ryujinx.Common.Logging;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Text;
using Ryujinx.Common.Logging;
namespace Ryujinx.Common.SystemInfo
{

View File

@ -1,8 +1,8 @@
using Ryujinx.Common.Logging;
using System;
using System.Management;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Ryujinx.Common.Logging;
namespace Ryujinx.Common.SystemInfo
{

View File

@ -0,0 +1,109 @@
using Microsoft.Win32.SafeHandles;
using Ryujinx.Common.Logging;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Common.SystemInterop
{
public partial class StdErrAdapter : IDisposable
{
private bool _disposable = false;
private Stream _pipeReader;
private Stream _pipeWriter;
private CancellationTokenSource _cancellationTokenSource;
private Task _worker;
public StdErrAdapter()
{
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
RegisterPosix();
}
}
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
private void RegisterPosix()
{
const int stdErrFileno = 2;
(int readFd, int writeFd) = MakePipe();
dup2(writeFd, stdErrFileno);
_pipeReader = CreateFileDescriptorStream(readFd);
_pipeWriter = CreateFileDescriptorStream(writeFd);
_cancellationTokenSource = new CancellationTokenSource();
_worker = Task.Run(async () => await EventWorkerAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
_disposable = true;
}
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
private async Task EventWorkerAsync(CancellationToken cancellationToken)
{
using TextReader reader = new StreamReader(_pipeReader, leaveOpen: true);
string line;
while (cancellationToken.IsCancellationRequested == false && (line = await reader.ReadLineAsync(cancellationToken)) != null)
{
Logger.Error?.PrintRawMsg(line);
}
}
private void Dispose(bool disposing)
{
if (_disposable)
{
_disposable = false;
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
_cancellationTokenSource.Cancel();
_worker.Wait(0);
_pipeReader?.Close();
_pipeWriter?.Close();
}
}
}
public void Dispose()
{
Dispose(true);
}
[LibraryImport("libc", SetLastError = true)]
private static partial int dup2(int fd, int fd2);
[LibraryImport("libc", SetLastError = true)]
private static partial int pipe(Span<int> pipefd);
private static (int, int) MakePipe()
{
Span<int> pipefd = stackalloc int[2];
if (pipe(pipefd) == 0)
{
return (pipefd[0], pipefd[1]);
}
else
{
throw new();
}
}
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
private static Stream CreateFileDescriptorStream(int fd)
{
return new FileStream(
new SafeFileHandle((IntPtr)fd, ownsHandle: true),
FileAccess.ReadWrite
);
}
}
}

View File

@ -1,3 +1,5 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using System;
using System.IO;
using System.Linq;
@ -38,12 +40,7 @@ namespace Ryujinx.Common
return null;
}
using (var mem = new MemoryStream())
{
stream.CopyTo(mem);
return mem.ToArray();
}
return StreamUtils.StreamToBytes(stream);
}
}
@ -56,12 +53,7 @@ namespace Ryujinx.Common
return null;
}
using (var mem = new MemoryStream())
{
await stream.CopyToAsync(mem);
return mem.ToArray();
}
return await StreamUtils.StreamToBytesAsync(stream);
}
}

View File

@ -1,4 +1,8 @@
using System.IO;
using Microsoft.IO;
using Ryujinx.Common.Memory;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Common.Utilities
{
@ -6,12 +10,22 @@ namespace Ryujinx.Common.Utilities
{
public static byte[] StreamToBytes(Stream input)
{
using (MemoryStream stream = new MemoryStream())
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
{
input.CopyTo(stream);
return stream.ToArray();
}
}
public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default)
{
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
{
await input.CopyToAsync(stream, cancellationToken);
return stream.ToArray();
}
}
}
}

View File

@ -1,4 +1,6 @@
using Ryujinx.Memory;
using Microsoft.IO;
using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using System;
using System.IO;
using System.Runtime.CompilerServices;
@ -40,7 +42,7 @@ namespace Ryujinx.Cpu
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)
{
using (MemoryStream ms = new MemoryStream())
using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream())
{
for (long offs = 0; offs < maxSize || maxSize == -1; offs++)
{
@ -54,7 +56,7 @@ namespace Ryujinx.Cpu
ms.WriteByte(value);
}
return Encoding.ASCII.GetString(ms.ToArray());
return Encoding.ASCII.GetString(ms.GetReadOnlySequence());
}
}
}

View File

@ -28,5 +28,10 @@ namespace Ryujinx.Cpu.Tracking
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size);
public bool RangeEquals(CpuRegionHandle other)
{
return _impl.RealAddress == other._impl.RealAddress && _impl.RealSize == other._impl.RealSize;
}
}
}

View File

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.GAL
{
public enum AntiAliasing
{
None,
Fxaa,
SmaaLow,
SmaaMedium,
SmaaHigh,
SmaaUltra
}
}

View File

@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsBlendEquationAdvanced;
public readonly bool SupportsFragmentShaderInterlock;
public readonly bool SupportsFragmentShaderOrderingIntel;
public readonly bool SupportsGeometryShader;
public readonly bool SupportsGeometryShaderPassthrough;
public readonly bool SupportsImageLoadFormatted;
public readonly bool SupportsLayerVertexTessellation;
@ -68,6 +69,7 @@ namespace Ryujinx.Graphics.GAL
bool supportsBlendEquationAdvanced,
bool supportsFragmentShaderInterlock,
bool supportsFragmentShaderOrderingIntel,
bool supportsGeometryShader,
bool supportsGeometryShaderPassthrough,
bool supportsImageLoadFormatted,
bool supportsLayerVertexTessellation,
@ -107,6 +109,7 @@ namespace Ryujinx.Graphics.GAL
SupportsBlendEquationAdvanced = supportsBlendEquationAdvanced;
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
SupportsGeometryShader = supportsGeometryShader;
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
SupportsImageLoadFormatted = supportsImageLoadFormatted;
SupportsLayerVertexTessellation = supportsLayerVertexTessellation;

View File

@ -15,7 +15,12 @@ namespace Ryujinx.Graphics.GAL
void BackgroundContextAction(Action action, bool alwaysBackground = false);
BufferHandle CreateBuffer(int size);
BufferHandle CreateBuffer(int size, BufferHandle storageHint);
BufferHandle CreateBuffer(int size)
{
return CreateBuffer(size, BufferHandle.Null);
}
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
@ -26,7 +31,7 @@ namespace Ryujinx.Graphics.GAL
void DeleteBuffer(BufferHandle buffer);
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
Capabilities GetCapabilities();
ulong GetCurrentSync();

View File

@ -15,8 +15,8 @@ namespace Ryujinx.Graphics.GAL
ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
ReadOnlySpan<byte> GetData();
ReadOnlySpan<byte> GetData(int layer, int level);
PinnedSpan<byte> GetData();
PinnedSpan<byte> GetData(int layer, int level);
void SetData(SpanOrArray<byte> data);
void SetData(SpanOrArray<byte> data, int layer, int level);

View File

@ -9,5 +9,9 @@ namespace Ryujinx.Graphics.GAL
void SetSize(int width, int height);
void ChangeVSyncMode(bool vsyncEnabled);
void SetAntiAliasing(AntiAliasing antialiasing);
void SetScalingFilter(ScalingFilter type);
void SetScalingFilterLevel(float level);
}
}

View File

@ -21,9 +21,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
public static void Run(ref BufferGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ReadOnlySpan<byte> result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size);
PinnedSpan<byte> result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size);
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
command._result.Get(threaded).Result = result;
}
}
}

View File

@ -5,16 +5,25 @@
public CommandType CommandType => CommandType.CreateBuffer;
private BufferHandle _threadedHandle;
private int _size;
private BufferHandle _storageHint;
public void Set(BufferHandle threadedHandle, int size)
public void Set(BufferHandle threadedHandle, int size, BufferHandle storageHint)
{
_threadedHandle = threadedHandle;
_size = size;
_storageHint = storageHint;
}
public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size));
BufferHandle hint = BufferHandle.Null;
if (command._storageHint != BufferHandle.Null)
{
hint = threaded.Buffers.MapBuffer(command._storageHint);
}
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, hint));
}
}
}

View File

@ -18,9 +18,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
public static void Run(ref TextureGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ReadOnlySpan<byte> result = command._texture.Get(threaded).Base.GetData();
PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData();
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
command._result.Get(threaded).Result = result;
}
}
}

View File

@ -22,9 +22,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
public static void Run(ref TextureGetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
ReadOnlySpan<byte> result = command._texture.Get(threaded).Base.GetData(command._layer, command._level);
PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData(command._layer, command._level);
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
command._result.Get(threaded).Result = result;
}
}
}

View File

@ -1,23 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.GAL.Multithreading.Model
{
unsafe struct PinnedSpan<T> where T : unmanaged
{
private void* _ptr;
private int _size;
public PinnedSpan(ReadOnlySpan<T> span)
{
_ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
_size = span.Length;
}
public ReadOnlySpan<T> Get()
{
return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
}
}
}

View File

@ -72,7 +72,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return newTex;
}
public ReadOnlySpan<byte> GetData()
public PinnedSpan<byte> GetData()
{
if (_renderer.IsGpuThread())
{
@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
_renderer.New<TextureGetDataCommand>().Set(Ref(this), Ref(box));
_renderer.InvokeCommand();
return box.Result.Get();
return box.Result;
}
else
{
@ -90,7 +90,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
}
}
public ReadOnlySpan<byte> GetData(int layer, int level)
public PinnedSpan<byte> GetData(int layer, int level)
{
if (_renderer.IsGpuThread())
{
@ -98,7 +98,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
_renderer.New<TextureGetDataSliceCommand>().Set(Ref(this), Ref(box), layer, level);
_renderer.InvokeCommand();
return box.Result.Get();
return box.Result;
}
else
{

View File

@ -265,10 +265,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading
}
}
public BufferHandle CreateBuffer(int size)
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
{
BufferHandle handle = Buffers.CreateBufferHandle();
New<CreateBufferCommand>().Set(handle, size);
New<CreateBufferCommand>().Set(handle, size, storageHint);
QueueCommand();
return handle;
@ -329,7 +329,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
QueueCommand();
}
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
{
if (IsGpuThread())
{
@ -337,7 +337,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
New<BufferGetDataCommand>().Set(buffer, offset, size, Ref(box));
InvokeCommand();
return box.Result.Get();
return box.Result;
}
else
{

View File

@ -32,5 +32,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
}
public void ChangeVSyncMode(bool vsyncEnabled) { }
public void SetAntiAliasing(AntiAliasing effect) { }
public void SetScalingFilter(ScalingFilter type) { }
public void SetScalingFilterLevel(float level) { }
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.GAL
{
public unsafe struct PinnedSpan<T> : IDisposable where T : unmanaged
{
private void* _ptr;
private int _size;
private Action _disposeAction;
/// <summary>
/// Creates a new PinnedSpan from an existing ReadOnlySpan. The span *must* be pinned in memory.
/// The data must be guaranteed to live until disposeAction is called.
/// </summary>
/// <param name="span">Existing span</param>
/// <param name="disposeAction">Action to call on dispose</param>
/// <remarks>
/// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
/// </remarks>
public static PinnedSpan<T> UnsafeFromSpan(ReadOnlySpan<T> span, Action disposeAction = null)
{
return new PinnedSpan<T>(Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)), span.Length, disposeAction);
}
/// <summary>
/// Creates a new PinnedSpan from an existing unsafe region. The data must be guaranteed to live until disposeAction is called.
/// </summary>
/// <param name="ptr">Pointer to the region</param>
/// <param name="size">The total items of T the region contains</param>
/// <param name="disposeAction">Action to call on dispose</param>
/// <remarks>
/// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
/// </remarks>
public PinnedSpan(void* ptr, int size, Action disposeAction = null)
{
_ptr = ptr;
_size = size;
_disposeAction = disposeAction;
}
public ReadOnlySpan<T> Get()
{
return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
}
public void Dispose()
{
_disposeAction?.Invoke();
}
}
}

View File

@ -20,5 +20,15 @@ namespace Ryujinx.Graphics.GAL
{
return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
}
public static bool HasDepthOrLayers(this Target target)
{
return target == Target.Texture3D ||
target == Target.Texture1DArray ||
target == Target.Texture2DArray ||
target == Target.Texture2DMultisampleArray ||
target == Target.Cubemap ||
target == Target.CubemapArray;
}
}
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.GAL
{
public enum ScalingFilter
{
Bilinear,
Nearest,
Fsr
}
}

View File

@ -180,7 +180,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
int firstInstance = (int)_state.State.FirstInstance;
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount();
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
if (inlineIndexCount != 0)
{
@ -670,7 +670,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
if (indexedInline)
{
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount();
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);

View File

@ -11,9 +11,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
struct IbStreamer
{
private const int BufferCapacity = 256; // Must be a power of 2.
private BufferHandle _inlineIndexBuffer;
private int _inlineIndexBufferSize;
private int _inlineIndexCount;
private uint[] _buffer;
private int _bufferOffset;
/// <summary>
/// Indicates if any index buffer data has been pushed.
@ -38,9 +42,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// Gets the number of elements on the current inline index buffer,
/// while also reseting it to zero for the next draw.
/// </summary>
/// <param name="renderer">Host renderer</param>
/// <returns>Inline index bufffer count</returns>
public int GetAndResetInlineIndexCount()
public int GetAndResetInlineIndexCount(IRenderer renderer)
{
UpdateRemaining(renderer);
int temp = _inlineIndexCount;
_inlineIndexCount = 0;
return temp;
@ -58,16 +64,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
byte i2 = (byte)(argument >> 16);
byte i3 = (byte)(argument >> 24);
Span<uint> data = stackalloc uint[4];
int offset = _inlineIndexCount;
data[0] = i0;
data[1] = i1;
data[2] = i2;
data[3] = i3;
int offset = _inlineIndexCount * 4;
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
PushData(renderer, offset, i0);
PushData(renderer, offset + 1, i1);
PushData(renderer, offset + 2, i2);
PushData(renderer, offset + 3, i3);
_inlineIndexCount += 4;
}
@ -82,14 +84,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ushort i0 = (ushort)argument;
ushort i1 = (ushort)(argument >> 16);
Span<uint> data = stackalloc uint[2];
int offset = _inlineIndexCount;
data[0] = i0;
data[1] = i1;
int offset = _inlineIndexCount * 4;
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
PushData(renderer, offset, i0);
PushData(renderer, offset + 1, i1);
_inlineIndexCount += 2;
}
@ -103,13 +101,61 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
uint i0 = (uint)argument;
Span<uint> data = stackalloc uint[1];
int offset = _inlineIndexCount++;
data[0] = i0;
PushData(renderer, offset, i0);
}
int offset = _inlineIndexCount++ * 4;
/// <summary>
/// Pushes a 32-bit value to the index buffer.
/// </summary>
/// <param name="renderer">Host renderer</param>
/// <param name="offset">Offset where the data should be written, in 32-bit words</param>
/// <param name="value">Index value to be written</param>
private void PushData(IRenderer renderer, int offset, uint value)
{
if (_buffer == null)
{
_buffer = new uint[BufferCapacity];
}
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
// We upload data in chunks.
// If we are at the start of a chunk, then the buffer might be full,
// in that case we need to submit any existing data before overwriting the buffer.
int subOffset = offset & (BufferCapacity - 1);
if (subOffset == 0 && offset != 0)
{
int baseOffset = (offset - BufferCapacity) * sizeof(uint);
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, BufferCapacity * sizeof(uint));
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer));
}
_buffer[subOffset] = value;
}
/// <summary>
/// Makes sure that any pending data is submitted to the GPU before the index buffer is used.
/// </summary>
/// <param name="renderer">Host renderer</param>
private void UpdateRemaining(IRenderer renderer)
{
int offset = _inlineIndexCount;
if (offset == 0)
{
return;
}
int count = offset & (BufferCapacity - 1);
if (count == 0)
{
count = BufferCapacity;
}
int baseOffset = (offset - count) * sizeof(uint);
int length = count * sizeof(uint);
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, length);
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer).Slice(0, length));
}
/// <summary>
@ -117,12 +163,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
/// <param name="renderer">Host renderer</param>
/// <param name="offset">Offset where the data will be written</param>
/// <param name="length">Number of bytes that will be written</param>
/// <returns>Buffer handle</returns>
private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset)
private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset, int length)
{
// Calculate a reasonable size for the buffer that can fit all the data,
// and that also won't require frequent resizes if we need to push more data.
int size = BitUtils.AlignUp(offset + 0x10, 0x200);
int size = BitUtils.AlignUp(offset + length + 0x10, 0x200);
if (_inlineIndexBuffer == BufferHandle.Null)
{

View File

@ -152,21 +152,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ulong ticks = _context.GetTimestamp();
float divisor = type switch
{
ReportCounterType.SamplesPassed => _channel.TextureManager.RenderTargetScale * _channel.TextureManager.RenderTargetScale,
_ => 1f
};
ICounterEvent counter = null;
void resultHandler(object evt, ulong result)
{
if (divisor != 1f)
{
result = (ulong)MathF.Ceiling(result / divisor);
}
CounterData counterData = new CounterData
{
Counter = result,

View File

@ -439,7 +439,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
int samplesInY = msaaMode.SamplesInY();
var scissor = _state.State.ScreenScissorState;
Size sizeHint = new Size(scissor.X + scissor.Width, scissor.Y + scissor.Height, 1);
Size sizeHint = new Size((scissor.X + scissor.Width) * samplesInX, (scissor.Y + scissor.Height) * samplesInY, 1);
int clipRegionWidth = int.MaxValue;
int clipRegionHeight = int.MaxValue;

View File

@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
public TexturePool Pool;
public int ID;
public ulong GpuAddress;
}
private GpuContext _context;
@ -162,6 +163,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public bool IsView => _viewStorage != this;
/// <summary>
/// Whether or not this texture has views.
/// </summary>
public bool HasViews => _views.Count > 0;
private int _referenceCount;
private List<TexturePoolOwner> _poolOwners;
@ -354,7 +360,7 @@ namespace Ryujinx.Graphics.Gpu.Image
texture._viewStorage = this;
Group.UpdateViews(_views);
Group.UpdateViews(_views, texture);
if (texture.Group != null && texture.Group != Group)
{
@ -378,11 +384,24 @@ namespace Ryujinx.Graphics.Gpu.Image
{
_views.Remove(texture);
Group.RemoveView(texture);
texture._viewStorage = texture;
DecrementReferenceCount();
}
/// <summary>
/// Replaces the texture's physical memory range. This forces tracking to regenerate.
/// </summary>
/// <param name="range">New physical memory range backing the texture</param>
public void ReplaceRange(MultiRange range)
{
Range = range;
Group.RangeChanged();
}
/// <summary>
/// Create a copy dependency to a texture that is view compatible with this one.
/// When either texture is modified, the texture data will be copied to the other to keep them in sync.
@ -715,6 +734,8 @@ namespace Ryujinx.Graphics.Gpu.Image
height = Math.Max(height >> level, 1);
depth = Math.Max(depth >> level, 1);
int sliceDepth = single ? 1 : depth;
SpanOrArray<byte> result;
if (Info.IsLinear)
@ -735,7 +756,7 @@ namespace Ryujinx.Graphics.Gpu.Image
width,
height,
depth,
single ? 1 : depth,
sliceDepth,
levels,
layers,
Info.FormatInfo.BlockWidth,
@ -759,7 +780,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Info.FormatInfo.BlockHeight,
width,
height,
depth,
sliceDepth,
levels,
layers,
out byte[] decoded))
@ -771,7 +792,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (GraphicsConfig.EnableTextureRecompression)
{
decoded = BCnEncoder.EncodeBC7(decoded, width, height, depth, levels, layers);
decoded = BCnEncoder.EncodeBC7(decoded, width, height, sliceDepth, levels, layers);
}
result = decoded;
@ -782,15 +803,15 @@ namespace Ryujinx.Graphics.Gpu.Image
{
case Format.Etc2RgbaSrgb:
case Format.Etc2RgbaUnorm:
result = ETC2Decoder.DecodeRgba(result, width, height, depth, levels, layers);
result = ETC2Decoder.DecodeRgba(result, width, height, sliceDepth, levels, layers);
break;
case Format.Etc2RgbPtaSrgb:
case Format.Etc2RgbPtaUnorm:
result = ETC2Decoder.DecodePta(result, width, height, depth, levels, layers);
result = ETC2Decoder.DecodePta(result, width, height, sliceDepth, levels, layers);
break;
case Format.Etc2RgbSrgb:
case Format.Etc2RgbUnorm:
result = ETC2Decoder.DecodeRgb(result, width, height, depth, levels, layers);
result = ETC2Decoder.DecodeRgb(result, width, height, sliceDepth, levels, layers);
break;
}
}
@ -800,31 +821,31 @@ namespace Ryujinx.Graphics.Gpu.Image
{
case Format.Bc1RgbaSrgb:
case Format.Bc1RgbaUnorm:
result = BCnDecoder.DecodeBC1(result, width, height, depth, levels, layers);
result = BCnDecoder.DecodeBC1(result, width, height, sliceDepth, levels, layers);
break;
case Format.Bc2Srgb:
case Format.Bc2Unorm:
result = BCnDecoder.DecodeBC2(result, width, height, depth, levels, layers);
result = BCnDecoder.DecodeBC2(result, width, height, sliceDepth, levels, layers);
break;
case Format.Bc3Srgb:
case Format.Bc3Unorm:
result = BCnDecoder.DecodeBC3(result, width, height, depth, levels, layers);
result = BCnDecoder.DecodeBC3(result, width, height, sliceDepth, levels, layers);
break;
case Format.Bc4Snorm:
case Format.Bc4Unorm:
result = BCnDecoder.DecodeBC4(result, width, height, depth, levels, layers, Format == Format.Bc4Snorm);
result = BCnDecoder.DecodeBC4(result, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
break;
case Format.Bc5Snorm:
case Format.Bc5Unorm:
result = BCnDecoder.DecodeBC5(result, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
result = BCnDecoder.DecodeBC5(result, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
break;
case Format.Bc6HSfloat:
case Format.Bc6HUfloat:
result = BCnDecoder.DecodeBC6(result, width, height, depth, levels, layers, Format == Format.Bc6HSfloat);
result = BCnDecoder.DecodeBC6(result, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
break;
case Format.Bc7Srgb:
case Format.Bc7Unorm:
result = BCnDecoder.DecodeBC7(result, width, height, depth, levels, layers);
result = BCnDecoder.DecodeBC7(result, width, height, sliceDepth, levels, layers);
break;
}
}
@ -1001,13 +1022,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// This method should be used to retrieve data that was modified by the host GPU.
/// This is not cheap, avoid doing that unless strictly needed.
/// </remarks>
/// <param name="output">An output span to place the texture data into. If empty, one is generated</param>
/// <param name="output">An output span to place the texture data into</param>
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
/// <returns>The span containing the texture data</returns>
private ReadOnlySpan<byte> GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null)
private void GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null)
{
ReadOnlySpan<byte> data;
PinnedSpan<byte> data;
if (texture != null)
{
@ -1033,9 +1053,9 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
data = ConvertFromHostCompatibleFormat(output, data);
ConvertFromHostCompatibleFormat(output, data.Get());
return data;
data.Dispose();
}
/// <summary>
@ -1050,10 +1070,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="level">The level of the texture to flush</param>
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
/// <returns>The span containing the texture data</returns>
public ReadOnlySpan<byte> GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
public void GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
{
ReadOnlySpan<byte> data;
PinnedSpan<byte> data;
if (texture != null)
{
@ -1079,9 +1098,9 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
data = ConvertFromHostCompatibleFormat(output, data, level, true);
ConvertFromHostCompatibleFormat(output, data.Get(), level, true);
return data;
data.Dispose();
}
/// <summary>
@ -1454,8 +1473,8 @@ namespace Ryujinx.Graphics.Gpu.Image
MultiRange otherRange = texture.Range;
IEnumerable<MultiRange> regions = _sizeInfo.AllRegions().Select((region) => Range.GetSlice((ulong)region.Offset, (ulong)region.Size));
IEnumerable<MultiRange> otherRegions = texture._sizeInfo.AllRegions().Select((region) => otherRange.GetSlice((ulong)region.Offset, (ulong)region.Size));
IEnumerable<MultiRange> regions = _sizeInfo.AllRegions().Select((region) => Range.Slice((ulong)region.Offset, (ulong)region.Size));
IEnumerable<MultiRange> otherRegions = texture._sizeInfo.AllRegions().Select((region) => otherRange.Slice((ulong)region.Offset, (ulong)region.Size));
foreach (MultiRange region in regions)
{
@ -1484,11 +1503,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="pool">The texture pool this texture has been added to</param>
/// <param name="id">The ID of the reference to this texture in the pool</param>
public void IncrementReferenceCount(TexturePool pool, int id)
/// <param name="gpuVa">GPU VA of the pool reference</param>
public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa)
{
lock (_poolOwners)
{
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id });
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa });
}
_referenceCount++;
@ -1585,6 +1605,36 @@ namespace Ryujinx.Graphics.Gpu.Image
InvalidatedSequence++;
}
/// <summary>
/// Queue updating texture mappings on the pool. Happens from another thread.
/// </summary>
public void UpdatePoolMappings()
{
lock (_poolOwners)
{
ulong address = 0;
foreach (var owner in _poolOwners)
{
if (address == 0 || address == owner.GpuAddress)
{
address = owner.GpuAddress;
owner.Pool.QueueUpdateMapping(this, owner.ID);
}
else
{
// If there is a different GPU VA mapping, prefer the first and delete the others.
owner.Pool.ForceRemove(this, owner.ID, true);
}
}
_poolOwners.Clear();
}
InvalidatedSequence++;
}
/// <summary>
/// Delete the texture if it is not used anymore.
/// The texture is considered unused when the reference count is zero,
@ -1636,7 +1686,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Group.ClearModified(unmapRange);
}
RemoveFromPools(true);
UpdatePoolMappings();
}
/// <summary>

View File

@ -194,6 +194,39 @@ namespace Ryujinx.Graphics.Gpu.Image
_cache.Lift(texture);
}
/// <summary>
/// Attempts to update a texture's physical memory range.
/// Returns false if there is an existing texture that matches with the updated range.
/// </summary>
/// <param name="texture">Texture to update</param>
/// <param name="range">New physical memory range</param>
/// <returns>True if the mapping was updated, false otherwise</returns>
public bool UpdateMapping(Texture texture, MultiRange range)
{
// There cannot be an existing texture compatible with this mapping in the texture cache already.
int overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps);
for (int i = 0; i < overlapCount; i++)
{
var other = _textureOverlaps[i];
if (texture != other &&
(texture.IsViewCompatible(other.Info, other.Range, true, other.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible ||
other.IsViewCompatible(texture.Info, texture.Range, true, texture.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible))
{
return false;
}
}
_textures.Remove(texture);
texture.ReplaceRange(range);
_textures.Add(texture);
return true;
}
/// <summary>
/// Tries to find an existing texture, or create a new one if not found.
/// </summary>

View File

@ -39,6 +39,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
class TextureGroup : IDisposable
{
/// <summary>
/// Threshold of layers to force granular handles (and thus partial loading) on array/3D textures.
/// </summary>
private const int GranularLayerThreshold = 8;
private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
/// <summary>
@ -116,7 +121,29 @@ namespace Ryujinx.Graphics.Gpu.Image
_allOffsets = size.AllOffsets;
_sliceSizes = size.SliceSizes;
(_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
if (Storage.Target.HasDepthOrLayers() && Storage.Info.GetSlices() > GranularLayerThreshold)
{
_hasLayerViews = true;
_hasMipViews = true;
}
else
{
(_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
// If the texture is partially mapped, fully subdivide handles immediately.
MultiRange range = Storage.Range;
for (int i = 0; i < range.Count; i++)
{
if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped)
{
_hasLayerViews = true;
_hasMipViews = true;
break;
}
}
}
RecalculateHandleRegions();
}
@ -249,7 +276,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
bool dirty = false;
bool anyModified = false;
bool anyUnmapped = false;
bool anyNotDirty = false;
for (int i = 0; i < regionCount; i++)
{
@ -294,20 +321,21 @@ namespace Ryujinx.Graphics.Gpu.Image
dirty |= handleDirty;
}
anyUnmapped |= handleUnmapped;
if (group.NeedsCopy)
{
// The texture we copied from is still being written to. Copy from it again the next time this texture is used.
texture.SignalGroupDirty();
}
_loadNeeded[baseHandle + i] = handleDirty && !handleUnmapped;
bool loadNeeded = handleDirty && !handleUnmapped;
anyNotDirty |= !loadNeeded;
_loadNeeded[baseHandle + i] = loadNeeded;
}
if (dirty)
{
if (anyUnmapped || (_handles.Length > 1 && (anyModified || split)))
if (anyNotDirty || (_handles.Length > 1 && (anyModified || split)))
{
// Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
@ -331,24 +359,56 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="regionCount">The number of handles to synchronize</param>
private void SynchronizePartial(int baseHandle, int regionCount)
{
int spanEndIndex = -1;
int spanBase = 0;
ReadOnlySpan<byte> dataSpan = ReadOnlySpan<byte>.Empty;
for (int i = 0; i < regionCount; i++)
{
if (_loadNeeded[baseHandle + i])
{
var info = GetHandleInformation(baseHandle + i);
// Ensure the data for this handle is loaded in the span.
if (spanEndIndex <= i - 1)
{
spanEndIndex = i;
if (_is3D)
{
// Look ahead to see how many handles need to be loaded.
for (int j = i + 1; j < regionCount; j++)
{
if (_loadNeeded[baseHandle + j])
{
spanEndIndex = j;
}
else
{
break;
}
}
}
var endInfo = spanEndIndex == i ? info : GetHandleInformation(baseHandle + spanEndIndex);
spanBase = _allOffsets[info.Index];
int spanLast = _allOffsets[endInfo.Index + endInfo.Layers * endInfo.Levels - 1];
int endOffset = Math.Min(spanLast + _sliceSizes[endInfo.BaseLevel + endInfo.Levels - 1], (int)Storage.Size);
int size = endOffset - spanBase;
dataSpan = _physicalMemory.GetSpan(Storage.Range.Slice((ulong)spanBase, (ulong)size));
}
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
for (int layer = 0; layer < info.Layers; layer++)
{
for (int level = 0; level < info.Levels; level++)
{
int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
int offset = _allOffsets[offsetIndex];
int endOffset = Math.Min(offset + _sliceSizes[info.BaseLevel + level], (int)Storage.Size);
int size = endOffset - offset;
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
ReadOnlySpan<byte> data = dataSpan.Slice(offset - spanBase);
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
@ -413,7 +473,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int endOffset = Math.Min(offset + _sliceSizes[level], (int)Storage.Size);
int size = endOffset - offset;
using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.GetSlice((ulong)offset, (ulong)size), tracked);
using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.Slice((ulong)offset, (ulong)size), tracked);
Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture);
}
@ -865,8 +925,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>A TextureGroupHandle covering the given views</returns>
private TextureGroupHandle GenerateHandles(int viewStart, int views)
{
int viewEnd = viewStart + views - 1;
(_, int lastLevel) = GetLayerLevelForView(viewEnd);
int offset = _allOffsets[viewStart];
int endOffset = (viewStart + views == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[viewStart + views];
int endOffset = _allOffsets[viewEnd] + _sliceSizes[lastLevel];
int size = endOffset - offset;
var result = new List<CpuRegionHandle>();
@ -926,7 +989,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Update the views in this texture group, rebuilding the memory tracking if required.
/// </summary>
/// <param name="views">The views list of the storage texture</param>
public void UpdateViews(List<Texture> views)
/// <param name="texture">The texture that has been added, if that is the only change, otherwise null</param>
public void UpdateViews(List<Texture> views, Texture texture)
{
// This is saved to calculate overlapping views for each handle.
_views = views;
@ -964,17 +1028,44 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!regionsRebuilt)
{
// Must update the overlapping views on all handles, but only if they were not just recreated.
foreach (TextureGroupHandle handle in _handles)
if (texture != null)
{
handle.RecalculateOverlaps(this, views);
int offset = FindOffset(texture);
foreach (TextureGroupHandle handle in _handles)
{
handle.AddOverlap(offset, texture);
}
}
else
{
// Must update the overlapping views on all handles, but only if they were not just recreated.
foreach (TextureGroupHandle handle in _handles)
{
handle.RecalculateOverlaps(this, views);
}
}
}
SignalAllDirty();
}
/// <summary>
/// Removes a view from the group, removing it from all overlap lists.
/// </summary>
/// <param name="view">View to remove from the group</param>
public void RemoveView(Texture view)
{
int offset = FindOffset(view);
foreach (TextureGroupHandle handle in _handles)
{
handle.RemoveOverlap(offset, view);
}
}
/// <summary>
/// Inherit handle state from an old set of handles, such as modified and dirty flags.
/// </summary>
@ -1057,7 +1148,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The dirty flags from the previous handles will be kept.
/// </summary>
/// <param name="handles">The handles to replace the current handles with</param>
private void ReplaceHandles(TextureGroupHandle[] handles)
/// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
private void ReplaceHandles(TextureGroupHandle[] handles, bool rangeChanged)
{
if (_handles != null)
{
@ -1065,9 +1157,50 @@ namespace Ryujinx.Graphics.Gpu.Image
foreach (TextureGroupHandle groupHandle in handles)
{
foreach (CpuRegionHandle handle in groupHandle.Handles)
if (rangeChanged)
{
handle.Reprotect();
// When the storage range changes, this becomes a little different.
// If a range does not match one in the original, treat it as modified.
// It has been newly mapped and its data must be synchronized.
if (groupHandle.Handles.Length == 0)
{
continue;
}
foreach (var oldGroup in _handles)
{
if (!groupHandle.OverlapsWith(oldGroup.Offset, oldGroup.Size))
{
continue;
}
foreach (CpuRegionHandle handle in groupHandle.Handles)
{
bool hasMatch = false;
foreach (var oldHandle in oldGroup.Handles)
{
if (oldHandle.RangeEquals(handle))
{
hasMatch = true;
break;
}
}
if (hasMatch)
{
handle.Reprotect();
}
}
}
}
else
{
foreach (CpuRegionHandle handle in groupHandle.Handles)
{
handle.Reprotect();
}
}
}
@ -1089,7 +1222,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
/// </summary>
private void RecalculateHandleRegions()
/// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
private void RecalculateHandleRegions(bool rangeChanged = false)
{
TextureGroupHandle[] handles;
@ -1171,7 +1305,21 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
ReplaceHandles(handles);
ReplaceHandles(handles, rangeChanged);
}
/// <summary>
/// Regenerates handles when the storage range has been remapped.
/// This forces the regions to be fully subdivided.
/// </summary>
public void RangeChanged()
{
_hasLayerViews = true;
_hasMipViews = true;
RecalculateHandleRegions(true);
SignalAllDirty();
}
/// <summary>
@ -1271,13 +1419,13 @@ namespace Ryujinx.Graphics.Gpu.Image
for (int i = 0; i < _allOffsets.Length; i++)
{
(int layer, int level) = GetLayerLevelForView(i);
MultiRange handleRange = Storage.Range.GetSlice((ulong)_allOffsets[i], 1);
MultiRange handleRange = Storage.Range.Slice((ulong)_allOffsets[i], 1);
ulong handleBase = handleRange.GetSubRange(0).Address;
for (int j = 0; j < other._handles.Length; j++)
{
(int otherLayer, int otherLevel) = other.GetLayerLevelForView(j);
MultiRange otherHandleRange = other.Storage.Range.GetSlice((ulong)other._allOffsets[j], 1);
MultiRange otherHandleRange = other.Storage.Range.Slice((ulong)other._allOffsets[j], 1);
ulong otherHandleBase = otherHandleRange.GetSubRange(0).Address;
if (handleBase == otherHandleBase)
@ -1354,7 +1502,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// Handles list is not modified by another thread, only replaced, so this is thread safe.
// Remove modified flags from all overlapping handles, so that the textures don't flush to unmapped/remapped GPU memory.
MultiRange subRange = Storage.Range.GetSlice((ulong)handle.Offset, (ulong)handle.Size);
MultiRange subRange = Storage.Range.Slice((ulong)handle.Offset, (ulong)handle.Size);
if (range.OverlapsWith(subRange))
{

View File

@ -159,6 +159,42 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
/// <summary>
/// Adds a single texture view as an overlap if its range overlaps.
/// </summary>
/// <param name="offset">The offset of the view in the group</param>
/// <param name="view">The texture to add as an overlap</param>
public void AddOverlap(int offset, Texture view)
{
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
if (OverlapsWith(offset, (int)view.Size))
{
lock (Overlaps)
{
Overlaps.Add(view);
}
}
}
/// <summary>
/// Removes a single texture view as an overlap if its range overlaps.
/// </summary>
/// <param name="offset">The offset of the view in the group</param>
/// <param name="view">The texture to add as an overlap</param>
public void RemoveOverlap(int offset, Texture view)
{
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
if (OverlapsWith(offset, (int)view.Size))
{
lock (Overlaps)
{
Overlaps.Remove(view);
}
}
}
/// <summary>
/// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle.
/// </summary>

View File

@ -1,9 +1,12 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image
{
@ -12,8 +15,63 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool>
{
/// <summary>
/// A request to dereference a texture from a pool.
/// </summary>
private struct DereferenceRequest
{
/// <summary>
/// Whether the dereference is due to a mapping change or not.
/// </summary>
public readonly bool IsRemapped;
/// <summary>
/// The texture being dereferenced.
/// </summary>
public readonly Texture Texture;
/// <summary>
/// The ID of the pool entry this reference belonged to.
/// </summary>
public readonly int ID;
/// <summary>
/// Create a dereference request for a texture with a specific pool ID, and remapped flag.
/// </summary>
/// <param name="isRemapped">Whether the dereference is due to a mapping change or not</param>
/// <param name="texture">The texture being dereferenced</param>
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
private DereferenceRequest(bool isRemapped, Texture texture, int id)
{
IsRemapped = isRemapped;
Texture = texture;
ID = id;
}
/// <summary>
/// Create a dereference request for a texture removal.
/// </summary>
/// <param name="texture">The texture being removed</param>
/// <returns>A texture removal dereference request</returns>
public static DereferenceRequest Remove(Texture texture)
{
return new DereferenceRequest(false, texture, 0);
}
/// <summary>
/// Create a dereference request for a texture remapping with a specific pool ID.
/// </summary>
/// <param name="texture">The texture being remapped</param>
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
/// <returns>A remap dereference request</returns>
public static DereferenceRequest Remap(Texture texture, int id)
{
return new DereferenceRequest(true, texture, id);
}
}
private readonly GpuChannel _channel;
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new ConcurrentQueue<DereferenceRequest>();
private TextureDescriptor _defaultDescriptor;
/// <summary>
@ -58,7 +116,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
TextureInfo info = GetInfo(descriptor, out int layerSize);
ProcessDereferenceQueue();
// The dereference queue can put our texture back on the cache.
if ((texture = ProcessDereferenceQueue(id)) != null)
{
return ref descriptor;
}
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
@ -68,11 +130,15 @@ namespace Ryujinx.Graphics.Gpu.Image
return ref descriptor;
}
}
texture.IncrementReferenceCount(this, id);
else
{
texture.SynchronizeMemory();
}
Items[id] = texture;
texture.IncrementReferenceCount(this, id, descriptor.UnpackAddress());
DescriptorCache[id] = descriptor;
}
else
@ -155,11 +221,14 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param>
public void ForceRemove(Texture texture, int id, bool deferred)
{
Items[id] = null;
var previous = Interlocked.Exchange(ref Items[id], null);
if (deferred)
{
_dereferenceQueue.Enqueue(texture);
if (previous != null)
{
_dereferenceQueue.Enqueue(DereferenceRequest.Remove(texture));
}
}
else
{
@ -167,16 +236,91 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
/// <summary>
/// Queues a request to update a texture's mapping.
/// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
/// </summary>
/// <param name="texture">Texture with potential mapping change</param>
/// <param name="id">ID in cache of texture with potential mapping change</param>
public void QueueUpdateMapping(Texture texture, int id)
{
if (Interlocked.Exchange(ref Items[id], null) == texture)
{
_dereferenceQueue.Enqueue(DereferenceRequest.Remap(texture, id));
}
}
/// <summary>
/// Process the dereference queue, decrementing the reference count for each texture in it.
/// This is used to ensure that texture disposal happens on the render thread.
/// </summary>
private void ProcessDereferenceQueue()
/// <param name="id">The ID of the entry that triggered this method</param>
/// <returns>Texture that matches the entry ID if it has been readded to the cache.</returns>
private Texture ProcessDereferenceQueue(int id = -1)
{
while (_dereferenceQueue.TryDequeue(out Texture toRemove))
while (_dereferenceQueue.TryDequeue(out DereferenceRequest request))
{
toRemove.DecrementReferenceCount();
Texture texture = request.Texture;
// Unmapped storage textures can swap their ranges. The texture must be storage with no views or dependencies.
// TODO: Would need to update ranges on views, or guarantee that ones where the range changes can be instantly deleted.
if (request.IsRemapped && texture.Group.Storage == texture && !texture.HasViews && !texture.Group.HasCopyDependencies)
{
// Has the mapping for this texture changed?
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(request.ID);
ulong address = descriptor.UnpackAddress();
MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
// If the texture is not mapped at all, delete its reference.
if (range.Count == 1 && range.GetSubRange(0).Address == MemoryManager.PteUnmapped)
{
texture.DecrementReferenceCount();
continue;
}
Items[request.ID] = texture;
// Create a new pool reference, as the last one was removed on unmap.
texture.IncrementReferenceCount(this, request.ID, address);
texture.DecrementReferenceCount();
// Refetch the range. Changes since the last check could have been lost
// as the cache entry was not restored (required to queue mapping change).
range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
if (!range.Equals(texture.Range))
{
// Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
{
// Texture could not be remapped due to a collision, just delete it.
if (Interlocked.Exchange(ref Items[request.ID], null) != null)
{
// If this is null, a request was already queued to decrement reference.
texture.DecrementReferenceCount(this, request.ID);
}
continue;
}
}
if (request.ID == id)
{
return texture;
}
}
else
{
texture.DecrementReferenceCount();
}
}
return null;
}
/// <summary>
@ -213,9 +357,10 @@ namespace Ryujinx.Graphics.Gpu.Image
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
}
texture.DecrementReferenceCount(this, id);
Items[id] = null;
if (Interlocked.Exchange(ref Items[id], null) != null)
{
texture.DecrementReferenceCount(this, id);
}
}
}
}

View File

@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
Address = address;
Size = size;
Handle = context.Renderer.CreateBuffer((int)size);
Handle = context.Renderer.CreateBuffer((int)size, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null);
_useGranular = size > GranularBufferThreshold;
@ -415,10 +415,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
int offset = (int)(address - Address);
ReadOnlySpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
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);
_physicalMemory.WriteUntracked(address, data.Get());
}
/// <summary>

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