Compare commits

..

71 Commits

Author SHA1 Message Date
ca59c3f499 Vulkan: Feedback loop detection and barriers (#7226)
* Vulkan: Feedback loop improvements

This PR allows the Vulkan backend to detect attachment feedback loops. These are currently used in the following ways:

- Partial use of VK_EXT_attachment_feedback_loop_layout
  - All renderable textures have AttachmentFeedbackLoopBitExt
  - Compile pipelines with Color/DepthStencil feedback loop flags when present
- Support using FragmentBarrier for feedback loops (fixes regressions from https://github.com/Ryujinx/Ryujinx/pull/7012 )

TODO:
- AMD GPUs may need layout transitions for it to properly allow textures to be used in feedback loops.
- Use dynamic state for feedback loops. The background pipeline will always miss since feedback loop state isn't known on the GPU project.
- How is the barrier dependency flag used? (DXVK just ignores it, there's no vulkan validation...)
- Improve subpass dependencies to fix validation errors

* Mark field readonly

* Add feedback loop dynamic state

* fix: add MoltenVK resolver workaround

fix: add MoltenVK resolver workaround

* Formatting

* Fix more complaints

* RADV dcc workaround

* Use dynamic state properly, cleanup.

* Use aspects flags in more places
2024-09-01 21:28:16 -03:00
fdd7ee791c Fix incorrect depth texture 3D flag (#7262) 2024-09-01 17:58:01 -03:00
398fa1c238 Vulkan: Update Silk.NET to 2.21 (#7266)
* Update Silk.NET version

* fix: add MoltenVK resolver workaround

fix: add MoltenVK resolver workaround

* Cleanup

* Readonly ref warnings

* Remove driver id todo
2024-09-01 17:33:11 -03:00
2c5c0392f9 Make HLE project AOT friendly (#7085)
* add hle service generator

remove usage of reflection in device state

* remove rd.xml generation

* make applet manager reflection free

* fix typos

* fix encoding

* fix style report

* remove rogue generator reference

* remove double assignment
2024-08-31 11:39:26 -03:00
e0acde04bb Replace ImageSharp with SkiaSharp everywhere (#7030)
* replace ImageSharp with SkiaSharp for inline keyboard applet rendering

* fix avalonia inline keyboard input

* remove image sharp from gtk3 project

* add skiasharp linux assets

* fix whitespace

* fix format

* fix ico image offset when saving shortcut to windows
2024-08-31 11:32:53 -03:00
3c61d560c3 Fix deadlock in background translation thread shutdown (#7239)
TryDequeue checks for _disposed before taking the lock.  If another
thread calls Dispose before it takes the lock, it won't get woken up by
the PulseAll call, and will deadlock in Monitor.Wait.

Double-checking _disposed with the lock taken should avoid this.
2024-08-27 19:10:24 +02:00
b45a81458a nuget: bump DynamicData from 9.0.1 to 9.0.4 (#7220)
Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 9.0.1 to 9.0.4.
- [Release notes](https://github.com/reactiveui/DynamicData/releases)
- [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md)
- [Commits](https://github.com/reactiveui/DynamicData/compare/9.0.1...9.0.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-21 12:30:43 +02:00
460f9faf4e Fix NRE when using buffer image array (#7159) 2024-08-21 00:49:17 +01:00
552c15739c nuget: bump ImageSharp from 2.1.8 to 2.1.9 (#7160)
While building I got some warnings, so I updated the dependency.

`warning NU1903: Package 'SixLabors.ImageSharp' 2.1.8 has a known high severity vulnerability, https://github.com/advisories/GHSA-63p8-c4ww-9cg7`
2024-08-20 22:26:32 +01:00
0137c9e635 nim:eca : Stub CreateServerInterface2 (#7128)
* Add files via upload

* Add files via upload

* Update src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2024-08-17 09:57:22 +01:00
23fa5f4c9c Fix arbitrary game ordering when sorting by Favorites (#7170)
* Fix arbitrary sorting by "Favorite" in the UI by making it the same as sorting alphabetically while giving favorites priority.

* Use a more engineered solution rather than string hacks.

* Address code style warnings. Add null checking. Make title name comparison case insensitive.

* one more style fix

---------

Co-authored-by: Logan Stromberg <lostromb@microsoft.com>
2024-08-13 15:23:11 +02:00
4f75e26ec7 Clamp amount of mipmap levels to max allowed for all backends (#7197)
* Clamp amount of mipmap levels to max allowed for all backends

* XML docs

* Remove using
2024-08-12 17:45:25 -03:00
8d8983049e Implement UQADD16, UQADD8, UQSUB16, UQSUB8, VQRDMULH, VSLI and VSWP Arm32 instructions (#7174) 2024-08-08 17:07:24 -03:00
7969fb6bba Replace and remove obsolete ByteMemoryPool type (#7155)
* refactor: replace usage of ByteMemoryPool with MemoryOwner<byte>

* refactor: delete unused ByteMemoryPool and ByteMemoryPool.ByteMemoryPoolBuffer types

* refactor: change IMemoryOwner<byte> return types to MemoryOwner<byte>

* fix(perf): get span via `MemoryOwner<T>.Span` directly instead of `MemoryOwner<T>.Memory.Span`

* fix(perf): get span via MemoryOwner<T>.Span directly instead of `MemoryOwner<T>.Memory.Span`

* fix(perf): get span via MemoryOwner<T>.Span directly instead of `MemoryOwner<T>.Memory.Span`
2024-08-05 21:09:08 -03:00
4a4b11871e Fix same textures with unmapped start being considered different (#7141)
* Fix same textures with unmapped start being considered different

* Consolidate IsInvalid check

* InvalidAddress const

* Fix typo

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

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2024-08-05 11:00:41 -03:00
e85ee673b1 Fix LocaleExtension SetRawSource usages + language perf improvement (#7121)
* Avoid Avalonia CompiledBindingPathBuilder.SetRawSource

* Improve UI language change performance
2024-08-04 19:04:12 +01:00
42f22fe5d7 Infra: Update Microsoft.IdentityModel.JsonWebTokens (#7070)
* Update Microsoft.IdentityModel.JsonWebTokens

* Update
2024-08-04 18:56:27 +01:00
263eb97f79 Avoid race conditions while launching games directly from the command line (#7116)
* optimization: Load application metadata only for applications with IDs

* Load applications when necessary

This prevents loading applications when launching an application
directly from the command line (or a shortcut).
Instead, applications will be loaded after the emulation was stopped by the user.

* Show the title in the configured language when launching an application

* Rename DesiredTitleLanguage to DesiredLanguage
2024-08-03 22:31:34 +01:00
3004902257 nuget: bump DynamicData from 8.4.1 to 9.0.1 (#7040)
Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 8.4.1 to 9.0.1.
- [Release notes](https://github.com/reactiveui/DynamicData/releases)
- [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md)
- [Commits](https://github.com/reactiveui/DynamicData/compare/8.4.1...9.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-03 22:34:41 +02:00
59ddb26628 replace ByteMemoryPool usage in Ryujinx.Graphics (#7129)
* chore: replace `ByteMemoryPool` usage with `MemoryOwner<byte>`

* refactor: `PixelConverter.ConvertR4G4ToR4G4B4A4()` - rename old `outputSpan` to `outputSpanUInt16`, reuse same output `Span<byte>` as newly-freed name `outputSpan`

* eliminate temporary buffer allocations

* chore, perf: use MemoryOwner<byte> instead of IMemoryOwner<byte>
2024-08-03 19:50:53 +01:00
83fda10f6e Fix FileNotFoundException in TryGetApplicationsFromFile() and improve loading applications (#7145)
* Don't load files from hidden subdirectories

* Catch FileNotFoundException in TryGetApplicationsFromFile()

* Skip non-existent files and bad symlinks when loading applications
2024-08-03 19:46:59 +02:00
d97e995e59 Fix off-by-one on audio renderer PerformanceManager.GetNextEntry (#7139) 2024-07-31 22:22:11 -03:00
56b2f84702 Fix shader RegisterUsage pass only taking first operation dest into account (#7131)
* Fix shader RegisterUsage pass only taking first operation dest into account

* Shader cache version bump
2024-07-30 21:57:55 -03:00
698e36bbd2 Vulkan: Force topology to PatchList for Tessellation (#7102)
Vulkan spec states that input topology should always be PatchList when a tessellation pipeline is present. The AMD GPU on windows crashes so hard it BSODs the machine if this isn't the case, so it's forced here just in case.

I'm not sure what providing a different topology here would even do, as you'd think it would always be a patch list input.
2024-07-30 21:48:30 -03:00
6ce49a2dc7 Ava UI: Handle updates containing non numeric characters (#7043)
* Handle updates containing non numeric characters

Smh

Dont be stupid

* Use Berry’s method

* Thanks gdk

* Remove using
2024-07-25 16:44:33 -03:00
ccd330ba0f Vulkan: Add missing barriers for texture to buffer copy (#7092)
This barrier has always been missing, but it only became apparent when #7012 merged.

I also added some barriers in case the target buffer used here is used by other commands, though right now it isn't.

Fixes a regression where water would turn white on AMD GPUs with the proprietary driver. May fix other issues on this driver.
2024-07-25 16:34:30 -03:00
95d252b7b8 Update kernel GetInfo SVC for firmware 18.0.0 (#7075)
* Implement kernel GetInfo AliasRegionExtraSize

* Implement IsSvcPermitted

* Remove warning supressions that are no longer needed

* Remove useless cast
2024-07-22 12:46:04 -03:00
add681144b Fix checking for the wrong metadata files for applications launched with a different program index (#7055)
* Fix checking for the wrong update metadata file

* Apply the same fix for dlc.json

* Use the base application ids for updates and DLCs in the GUI too

This shouldn't actually change anything, since the program index part of the application id
should always be 0 for all applications currently seen by the GUI.

This was just done for completeness.
2024-07-21 14:42:23 -03:00
c6dc00815a Make sure TryGetApplicationsFromFile() doesn't throw exceptions anymore (#7046)
* Add docstrings for exceptions to methods near TryGetApplicationsFromFile()

* Make sure TryGetApplicationsFromFile() doesn't throw exceptions anymore

* Add missing filePath to ApplicationData when loading applications from ExeFS

* Fix typo

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

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2024-07-20 16:35:43 -03:00
99f04ac1a6 Fix Skia saving screenshot with transparent background and incorrect origin (#7073)
* Fix Skia saving screenshot with transparent background and incorrect origin

* Remove code that is no longer necessary
2024-07-20 16:27:40 -03:00
ce09450743 Unlink server sessions from multi-wait when service stops processing requests (#7072) 2024-07-20 16:17:40 -03:00
2cb80f37d4 Ava UI: Auto select newly added updates & DLC (#7026)
* Fix DLC not being selected

* FIx conflicts

* Apply suggestions from code review

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

---------

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2024-07-19 19:00:15 +02:00
827069e784 Add missing Buffer attribute on NGC Check method (#7051) 2024-07-18 15:11:00 -03:00
1a919e99b2 Vulkan: Defer guest barriers, and improve image barrier timings (#7012)
* More guarantees for buffer correct placement, defer guest requested buffers

* Split RP on indirect barrier rn

* Better handling for feedback loops.

* Qualcomm barriers suck too

* Fix condition

* Remove unused field

* Allow render pass barriers on turnip for now
2024-07-17 20:21:32 -03:00
f77bebac80 Include content data foreach-loop in try-catch (#7036) 2024-07-17 19:02:20 -03:00
6fbf279fac Add support for multi game XCIs (second try) (#6515)
* Add default values to ApplicationData directly

* Refactor application loading

It should now be possible to load multi game XCIs.
Included updates won't be detected for now.
Opening a game from the command line currently only opens the first one.

* Only include program NCAs where at least one tuple item is not null

* Get application data by title id and add programIndex check back

* Refactor application loading again and remove duplicate code

* Actually use patch ncas for updates

* Fix number of applications found with multi game xcis

* Don't load bundled updates from multi game xcis

* Change ApplicationData.TitleId type to ulong & Add TitleIdString property

* Use cnmt files and ContentCollection to load programs

* Ava: Add updates and DLCs from gamecarts

* Get the cnmt file from its NCA

* Ava: Identify bundled updates in updater window

* Fix the (hopefully) last few bugs

* Add idOffset parameter to GetNcaByType

* Handle missing file for dlc.json

* Ava: Shorten error message for invalid files

* Gtk: Add additional string for bundled updates in TitleUpdateWindow

* Hopefully fix DLC issues

* Apply formatting

* Finally fix DLC issues

* Adjust property names and fileSize field

* Read the correct update file

* Fix wrong casing for application id strings

* Rename TitleId to ApplicationId

* Address review comments

* Apply suggestions from code review

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

* Gracefully fail when loading pfs for update and dlc window

* Fix applications with multiple programs

* Fix DLCWindow crash on GTK

* Fix some GUI issues

* Remove IsXci again

* Don't add duplicates to update/dlc windows

* Avoid double lookup

* Preserve DLC enabled state for bundled DLCs

* Fix DLCWindow not opening using GTK

* Fix missing information when loading applications from file

* Address review feedback

Rename ContentCollection to ContentMetaData
Fix casing issues in log messages
Use null as the default value for updatePath

* Fix re-adding bundled DLCs every time

* Fix bundled DLCs disappearing

* Abstract common code to open application pfs

* Remove unused imports

* Fix file exists check when loading DLCs

* Load bundled DLCs only using dlc.json

* Load AoC items correctly

* Add all DLCs from a PFS

* Add argument to launch a specific application id

* Use application-id argument for shortcuts if necessary

* Return the application id from the control NCA if possible

* GetApplicationInformation: Don't overwrite application ids

Move SaveDataOwnerId check to the top, since it seems to be more reliable.

* Get application ids from CNMT again

This commit reverts some parts of 61615b8f0d6f90ae86778958ddc38eaf6dc280ab.
Since the issue wasn't actually related to the application id in CMNTs, we can remove the wrong assumptions.

* Revert erroneous axaml change from adca8900

* Rename title to application

* Wrap nsp/pfs0 case with curly braces

* Check if _applicationData.ControlHolder.ByteSpan is zeros only once

* Catch exceptions while loading applications from nsps

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2024-07-16 18:17:32 -03:00
344f4f52c1 Remove CommandBufferScoped Dependencies (#6958) 2024-07-16 17:01:06 -03:00
eb212aa91b misc: Re-order and manually update DriverID to name. (#7027)
* Re-order and update DriverID -> Name.

* Fix whitespace
2024-07-15 19:27:59 -03:00
a6dbb2ad2b replace ByteMemoryPool usage in Ryujinx.HLE (#6953) 2024-07-15 19:21:53 -03:00
595e514f18 Use SkiaSharp for Avalonia in place of ImageSharp (#6269)
* Rebased

Transformation all at once

Use SkiaSharp instead of ImageSharp

* Apply suggestions from code review

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

* Change back unintentionally changed comment

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: Emmanuel Hansen <emmausssss@gmail.com>
2024-07-14 08:16:14 +00:00
07435ad844 Use draw clear on Adreno, instead of vkCmdClearAttachments (#7013)
* Use draw clear on Adreno, instead of vkCmdClearAttachments

* Fix GTX TITAN detection
2024-07-10 17:52:45 -03:00
1668ba913f Force dynamic state update after rasterizer discard disable (#7007) 2024-07-09 23:31:01 -03:00
a830eb666b Disallow concurrent fence waits on Adreno (#7001)
* Disallow concurrent fence waits on Adreno

* Ensure locks are released if exceptions are thrown
2024-07-07 19:33:28 -03:00
cfc75d7e78 Disable descriptor set template updates for buffer textures on Adreno (#7002)
* Do not use template updates for buffer textures and buffer images

* No need to do it for images

* Simply buffer texture existence check

* Pipeline is now unused on DescriptorSetUpdater
2024-07-07 19:19:55 -03:00
c525d7d9a9 Force Vulkan swapchain re-creation when window size changes (#7003) 2024-07-07 19:02:11 -03:00
1a0a351a15 Resolve some Vulkan validation errors (#6915)
* Fix some validation errors

* Whitespace correction

* Resolve some runtime validation errors.

* Whitespace

* Properly fix usage realted validation error by setting Extended Usage image creation flag.

* Only if supported

* Remove checking extension for features that are core functionality of Vulkan 1.2
2024-06-26 09:21:44 -03:00
bd3335c143 Make sure the string is long enough before performing basic trim (#6982) 2024-06-26 11:27:23 +02:00
a94445b23e nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.6.0 to 7.6.2 (#6965)
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.6.0 to 7.6.2.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/7.6.2/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/7.6.0...7.6.2)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  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>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2024-06-26 10:45:51 +02:00
0c3421973c SetProcessMemoryPermission address and size are always 64-bit (#6977) 2024-06-25 09:40:53 +02:00
0afa8f2c14 JIT: Coalesce copies on LSRA with simple register preferencing (#6950)
* JIT: Coalesce copies on LSRA with simple register preferencing

* PPTC version bump
2024-06-19 09:39:29 -03:00
d25a084858 JIT: Ensure entry block has no predecessors on RegisterUsage pass (#6951) 2024-06-19 09:25:47 -03:00
311ca3c3f1 fix: for pooled memory used for reference types, clear it on return to the pool so that it doesn't prevent GC of the instances it contained (#6937) 2024-06-16 17:47:47 -03:00
3193ef1083 Extend bindless elimination to catch a few more specific cases (#6921)
* Catch more cases on bindless elimination

* Match blocks with the same comparison condition

* Shader cache version bump
2024-06-16 14:46:27 -03:00
5a878ae9af replace ByteMemoryPool use with MemoryOwner<byte> and SpanOwner<byte> (#6911) 2024-06-15 23:00:13 +02:00
1828bc949e nuget: bump Microsoft.IO.RecyclableMemoryStream from 3.0.0 to 3.0.1 (#6936)
Bumps [Microsoft.IO.RecyclableMemoryStream](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream/releases)
- [Changelog](https://github.com/microsoft/Microsoft.IO.RecyclableMemoryStream/blob/master/CHANGES.md)
- [Commits](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream/compare/3.0.0...v3.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-15 22:51:50 +02:00
c0f2491eae Vulkan separate descriptor set fixes (#6895)
* Ensure descriptor sets are only re-used when all command buffers using it have completed

* Fix some SPIR-V capabilities

* Set update after bind flag if we exceed limits

* Simpler fix for Intel

* Format whitespace

* Make struct readonly

* Add barriers for extra set arrays too
2024-06-02 22:40:28 -03:00
d7c6474729 GPU: Remove unused dynamic state and pipeline settings (#6796)
* Dynamic state for Depth Bounds should not be passed to PipelineDynamicStateCreateInfo as the command to set them is never called.

Do not pass pointer to viewport and scissor as those dynamic states should be supported on all devices.

Same as above for DepthBias values.

* Code Review Suggestion

* Pipeline derivation is not implemented and is not suggested.

* Depth Bounds are not used.
2024-06-02 22:32:10 -03:00
1ecc8fbc3b New pooled memory types (#6821)
* feat: add new types MemoryOwner and SpanOwner

* use SpanOwner instead of new array allocation

* change for loop condition to `fences.Length` instead of `count` to elide Span boundary checks on `fences`
2024-06-02 22:24:14 -03:00
888402ecaf Avoid inexact read with 'Stream.Read' (#6847) 2024-06-02 22:16:48 +02:00
971d24aef0 nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.5.2 to 7.6.0 (#6893)
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.5.2 to 7.6.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/7.5.2...7.6.0)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-02 22:10:47 +02:00
c41fddd25e Vulkan: Extend full bindless to cover cases with phi nodes (#6853)
* Key textures using set and binding (rather than just binding)

* Extend full bindless to cover cases with phi nodes

* Log error on bindless access failure

* Shader cache version bump

* Remove constant buffer match to reduce the chances of full bindless triggering

* Re-enable it for constant buffers, paper mario does actually need it

* Format whitespace
2024-05-26 15:20:10 -03:00
2ebe929fa5 misc: Change disk shader cache compression algorithm to Brotli (RFC 7932) (#6841)
* Prefer `Brotli` compression for disk shader cache.

* Final default case for decompression switch.

* Prefer fastest compression.
2024-05-26 20:06:41 +02:00
53d096e392 Allow texture arrays to use separate descriptor sets on Vulkan (#6870)
* Report base and extra sets from the backend

* Pass texture set index everywhere

* Key textures using set and binding (rather than just binding)

* Start using extra sets for array textures

* Shader cache version bump

* Separate new commands, some PR feedback

* Introduce new manual descriptor set reservation method that prevents it from being used by something else while owned by an array

* Move bind extra sets logic to new method

* Should only use separate array is MaximumExtraSets is not zero

* Format whitespace
2024-05-26 13:30:19 -03:00
4cc00bb4b1 nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.5.1 to 7.5.2 (#6809)
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.5.1 to 7.5.2.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/7.5.1...7.5.2)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 05:35:49 +02:00
c98b7fc702 Workaround bug on logic op with float framebuffer (#6858)
* intel workaround

built on top of the amd workaround

* forgot to update the note

* Logic Change

Enabled workaround for all vendors that aren't nvidia

* Applied Suggestions
2024-05-23 22:57:26 -03:00
e65effcb05 Workaround AMD bug on logic op with float framebuffer (#6852)
* Workaround AMD bug on logic op with float framebuffer

* Format whitespace

* Update comment
2024-05-23 01:05:32 -03:00
c1ed150949 Kernel: Wake cores from idle directly rather than through a host thread (#6837)
* Kernel: Wake cores from idle directly rather than through a host thread

Right now when a core enters an idle state, leaving that idle state requires us to first signal the core's idle thread, which then signals the correct thread that we want to run on the core. This means that in a lot of cases, we're paying double for a thread to be woken from an idle state.

This PR moves this process to happen on the thread that is waking others out of idle, instead of an idle thread that needs to be woken first.

For compatibility the process has been kept as similar as possible - the process for IdleThreadLoop has been migrated to TryLeaveIdle, and is gated by a condition variable that lets it run only once at a time for each core. A core is only considered for wake from idle if idle is both active and has been signalled - the signal is consumed and the active state is cleared when the core leaves idle.

Dummy threads (just the idle thread at the moment) have been changed to have no host thread, as the work is now done by threads entering idle and signalling out of it.

This could put a bit of extra work on threads that would have triggered `_idleInterruptEvent` before, but I'd expect less work than signalling all those reset events and the OS overhead that follows. Worst case is that other threads performing these signals at the same time will have to wait for each other, but it's still going to be a very short amount of time.

Improvements are best seen in games with heavy (or very misguided) multithreading, such as Pokemon: Legends Arceus. Improvements are expected in Scarlet/Violet and TOTK, but are harder to measure.

Testing on Linux/MacOS still to be done, definitely need to test more games as this affects all of them (obviously) and any issues might be rare to encounter.

* Remove _idleThread entirely

* Use spinwait so we don't completely blast the CPU with cmpxchg

* Didn't I already do this

* Cleanup
2024-05-22 17:47:27 -03:00
c634eb4054 Updating Concentus dependency to speed up Opus decoding (#6757)
* Implementing new features in the latest Concentus library - span-in, span-out Opus decoding (so we don't have to make temporary buffer copies), returning a more precise error code from the decoder, and automatically linking the native opus library with P/invoke if supported on the current system

* Remove stub log messages and commit package upgrade to 2.1.0

* use more correct disposal pattern

* Bump to Concentus 2.1.1

* Bump to Concentus 2.1.2

* Don't bother pulling in native opus binaries from Concentus package (using ExcludeAssets).

* Fix opus MS channel count. Explicitly disable native lib probe in OpusCodecFactory.

* Bump to package 2.2.0 which has split out the native libs, as suggested.

---------

Co-authored-by: Logan Stromberg <lostromb@microsoft.com>
2024-05-20 18:38:38 -03:00
eb1ce41b00 GPU: Migrate buffers on GPU project, pre-emptively flush device local mappings (#6794)
* GPU: Migrate buffers on GPU project, pre-emptively flush device local mappings

Essentially retreading #4540, but it's on the GPU project now instead of the backend. This allows us to have a lot more control + knowledge of where the buffer backing has been changed and allows us to pre-emptively flush pages to host memory for quicker readback. It will allow us to do other stuff in the future, but we'll get there when we get there.

Performance greatly improved in Hyrule Warriors: Age of Calamity. Performance notably improved in TOTK (average). Performance for BOTW restored to how it was before #4911, perhaps a bit better.

- Rewrites a bunch of buffer migration stuff. Might want to tighten up how dispose stuff works.
- Fixed an issue where the copy for texture pre-flush would happen _after_ the syncpoint.

TODO: remove a page from pre-flush if it isn't flushed after a certain number of copies.

* Add copy deactivation

* Fix dependent virtual buffers

* Remove logging

* Fix format issues (maybe)

* Vulkan: Remove backing swap

* Add explicit memory access types for most buffers

* Fix typo

* Add device local force expiry, change buffer inheritance behaviour

* General cleanup, OGL fix

* BufferPreFlush comments

* BufferBackingState comments

* Add an extra precaution to BufferMigration

This is very unlikely, but it's important to cover loose ends like this.

* Address some feedback

* Docs
2024-05-19 16:53:37 -03:00
2f427deb67 Fix another NullReferenceException (#6826) 2024-05-17 20:11:30 -03:00
8f51938e2b Disable keyboard controller input while swkbd is open (foreground) (second attempt) (#6808)
* Block input updates while swkbd is open in foreground mode

* Flush internal driver state before unblocking input updates

* Rename Flush to Clear and remove unnecessary attribute

* Clear the driver state only if the GamepadDriver isn't null
2024-05-17 16:58:03 -03:00
294 changed files with 7781 additions and 3491 deletions

View File

@ -11,18 +11,18 @@
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" /> <PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" /> <PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" /> <PackageVersion Include="Concentus" Version="2.2.0" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="8.4.1" /> <PackageVersion Include="DynamicData" Version="9.0.4" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" /> <PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" /> <PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" /> <PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="LibHac" Version="0.19.0" /> <PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.5.1" /> <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" /> <PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" /> <PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NetCoreServer" Version="8.0.7" /> <PackageVersion Include="NetCoreServer" Version="8.0.7" />
<PackageVersion Include="NUnit" Version="3.13.3" /> <PackageVersion Include="NUnit" Version="3.13.3" />
@ -39,14 +39,14 @@
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" /> <PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" /> <PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.21.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.8" /> <PackageVersion Include="SkiaSharp" Version="2.88.7" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
<PackageVersion Include="SPB" Version="0.0.4-build32" /> <PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" /> <PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
<PackageVersion Include="System.Management" Version="8.0.0" /> <PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" /> <PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -249,6 +251,10 @@ Global
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -237,7 +237,7 @@ namespace ARMeilleure.CodeGen.Arm64
long originalPosition = _stream.Position; long originalPosition = _stream.Position;
_stream.Seek(0, SeekOrigin.Begin); _stream.Seek(0, SeekOrigin.Begin);
_stream.Read(code, 0, code.Length); _stream.ReadExactly(code, 0, code.Length);
_stream.Seek(originalPosition, SeekOrigin.Begin); _stream.Seek(originalPosition, SeekOrigin.Begin);
RelocInfo relocInfo; RelocInfo relocInfo;

View File

@ -251,7 +251,20 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
} }
} }
int selectedReg = GetHighestValueIndex(freePositions); // If this is a copy destination variable, we prefer the register used for the copy source.
// If the register is available, then the copy can be eliminated later as both source
// and destination will use the same register.
int selectedReg;
if (current.TryGetCopySourceRegister(out int preferredReg) && freePositions[preferredReg] >= current.GetEnd())
{
selectedReg = preferredReg;
}
else
{
selectedReg = GetHighestValueIndex(freePositions);
}
int selectedNextUse = freePositions[selectedReg]; int selectedNextUse = freePositions[selectedReg];
// Intervals starts and ends at odd positions, unless they span an entire // Intervals starts and ends at odd positions, unless they span an entire
@ -431,7 +444,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
} }
} }
private static int GetHighestValueIndex(Span<int> span) private static int GetHighestValueIndex(ReadOnlySpan<int> span)
{ {
int highest = int.MinValue; int highest = int.MinValue;
@ -798,12 +811,12 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
// The "visited" state is stored in the MSB of the local's value. // The "visited" state is stored in the MSB of the local's value.
const ulong VisitedMask = 1ul << 63; const ulong VisitedMask = 1ul << 63;
bool IsVisited(Operand local) static bool IsVisited(Operand local)
{ {
return (local.GetValueUnsafe() & VisitedMask) != 0; return (local.GetValueUnsafe() & VisitedMask) != 0;
} }
void SetVisited(Operand local) static void SetVisited(Operand local)
{ {
local.GetValueUnsafe() |= VisitedMask; local.GetValueUnsafe() |= VisitedMask;
} }
@ -826,9 +839,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{ {
dest.NumberLocal(_intervals.Count); dest.NumberLocal(_intervals.Count);
_intervals.Add(new LiveInterval(dest)); LiveInterval interval = new LiveInterval(dest);
_intervals.Add(interval);
SetVisited(dest); SetVisited(dest);
// If this is a copy (or copy-like operation), set the copy source interval as well.
// This is used for register preferencing later on, which allows the copy to be eliminated
// in some cases.
if (node.Instruction == Instruction.Copy || node.Instruction == Instruction.ZeroExtend32)
{
Operand source = node.GetSource(0);
if (source.Kind == OperandKind.LocalVariable &&
source.GetLocalNumber() > 0 &&
(node.Instruction == Instruction.Copy || source.Type == OperandType.I32))
{
interval.SetCopySource(_intervals[source.GetLocalNumber()]);
}
}
} }
} }
} }

View File

@ -19,6 +19,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
public LiveRange CurrRange; public LiveRange CurrRange;
public LiveInterval Parent; public LiveInterval Parent;
public LiveInterval CopySource;
public UseList Uses; public UseList Uses;
public LiveIntervalList Children; public LiveIntervalList Children;
@ -37,6 +38,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
private ref LiveRange CurrRange => ref _data->CurrRange; private ref LiveRange CurrRange => ref _data->CurrRange;
private ref LiveRange PrevRange => ref _data->PrevRange; private ref LiveRange PrevRange => ref _data->PrevRange;
private ref LiveInterval Parent => ref _data->Parent; private ref LiveInterval Parent => ref _data->Parent;
private ref LiveInterval CopySource => ref _data->CopySource;
private ref UseList Uses => ref _data->Uses; private ref UseList Uses => ref _data->Uses;
private ref LiveIntervalList Children => ref _data->Children; private ref LiveIntervalList Children => ref _data->Children;
@ -78,6 +80,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
Register = register; Register = register;
} }
public void SetCopySource(LiveInterval copySource)
{
CopySource = copySource;
}
public bool TryGetCopySourceRegister(out int copySourceRegIndex)
{
if (CopySource._data != null)
{
copySourceRegIndex = CopySource.Register.Index;
return true;
}
copySourceRegIndex = 0;
return false;
}
public void Reset() public void Reset()
{ {
PrevRange = default; PrevRange = default;

View File

@ -1444,7 +1444,7 @@ namespace ARMeilleure.CodeGen.X86
Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position]; Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
_stream.Read(buffer); _stream.ReadExactly(buffer);
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current); _stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
codeStream.Write(buffer); codeStream.Write(buffer);

View File

@ -822,6 +822,10 @@ namespace ARMeilleure.Decoders
SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create); SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create);
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create); SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create); SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create);
SetA32("<<<<01100110xxxxxxxx11110001xxxx", InstName.Uqadd16, InstEmit32.Uqadd16, OpCode32AluReg.Create);
SetA32("<<<<01100110xxxxxxxx11111001xxxx", InstName.Uqadd8, InstEmit32.Uqadd8, OpCode32AluReg.Create);
SetA32("<<<<01100110xxxxxxxx11110111xxxx", InstName.Uqsub16, InstEmit32.Uqsub16, OpCode32AluReg.Create);
SetA32("<<<<01100110xxxxxxxx11111111xxxx", InstName.Uqsub8, InstEmit32.Uqsub8, OpCode32AluReg.Create);
SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create); SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create);
SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create); SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create);
SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create); SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create);
@ -1007,6 +1011,8 @@ namespace ARMeilleure.Decoders
SetAsimd("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); SetAsimd("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32); SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32); SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
SetAsimd("111100110x01xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x10xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); SetAsimd("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
@ -1030,6 +1036,7 @@ namespace ARMeilleure.Decoders
SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding. SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding.
SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("111100111x>>>xxxxxxx0101>xx1xxxx", InstName.Vsli, InstEmit32.Vsli_I, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
@ -1054,6 +1061,7 @@ namespace ARMeilleure.Decoders
SetAsimd("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); SetAsimd("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create, OpCode32SimdRegLong.CreateT32); SetAsimd("1111001x1x<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create, OpCode32SimdRegLong.CreateT32);
SetAsimd("1111001x1x<<xxxxxxx00011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create, OpCode32SimdRegWide.CreateT32); SetAsimd("1111001x1x<<xxxxxxx00011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create, OpCode32SimdRegWide.CreateT32);
SetAsimd("111100111x110010xxxx00000xx0xxxx", InstName.Vswp, InstEmit32.Vswp, OpCode32Simd.Create, OpCode32Simd.CreateT32);
SetAsimd("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create, OpCode32SimdTbl.CreateT32); SetAsimd("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create, OpCode32SimdTbl.CreateT32);
SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32); SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);

View File

@ -2,6 +2,8 @@ using ARMeilleure.Decoders;
using ARMeilleure.IntermediateRepresentation; using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State; using ARMeilleure.State;
using ARMeilleure.Translation; using ARMeilleure.Translation;
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using static ARMeilleure.Instructions.InstEmitAluHelper; using static ARMeilleure.Instructions.InstEmitAluHelper;
using static ARMeilleure.Instructions.InstEmitHelper; using static ARMeilleure.Instructions.InstEmitHelper;
@ -558,6 +560,46 @@ namespace ARMeilleure.Instructions
EmitHsub8(context, unsigned: true); EmitHsub8(context, unsigned: true);
} }
public static void Uqadd16(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqadd(context, d, context.Add(n, m), 16);
}));
}
public static void Uqadd8(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqadd(context, d, context.Add(n, m), 8);
}));
}
public static void Uqsub16(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqsub(context, d, context.Subtract(n, m), 16);
}));
}
public static void Uqsub8(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqsub(context, d, context.Subtract(n, m), 8);
}));
}
public static void Usat(ArmEmitterContext context) public static void Usat(ArmEmitterContext context)
{ {
OpCode32Sat op = (OpCode32Sat)context.CurrOp; OpCode32Sat op = (OpCode32Sat)context.CurrOp;
@ -934,6 +976,148 @@ namespace ARMeilleure.Instructions
} }
} }
private static void EmitSaturateUqadd(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
{
Debug.Assert(saturateTo <= 32);
if (saturateTo == 32)
{
// No saturation possible for this case.
context.Copy(result, value);
return;
}
else if (saturateTo == 0)
{
// Result is always zero if we saturate 0 bits.
context.Copy(result, Const(0));
return;
}
// If the result is 0, the values are equal and we don't need saturation.
Operand lblNoSat = Label();
context.BranchIfFalse(lblNoSat, context.ShiftRightUI(value, Const((int)saturateTo)));
// Saturate.
context.Copy(result, Const(uint.MaxValue >> (32 - (int)saturateTo)));
Operand lblExit = Label();
context.Branch(lblExit);
context.MarkLabel(lblNoSat);
context.Copy(result, value);
context.MarkLabel(lblExit);
}
private static void EmitSaturateUqsub(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
{
Debug.Assert(saturateTo <= 32);
if (saturateTo == 32)
{
// No saturation possible for this case.
context.Copy(result, value);
return;
}
else if (saturateTo == 0)
{
// Result is always zero if we saturate 0 bits.
context.Copy(result, Const(0));
return;
}
// If the result is 0, the values are equal and we don't need saturation.
Operand lblNoSat = Label();
context.BranchIf(lblNoSat, value, Const(0), Comparison.GreaterOrEqual);
// Saturate.
// Assumes that the value can only underflow, since this is only used for unsigned subtraction.
context.Copy(result, Const(0));
Operand lblExit = Label();
context.Branch(lblExit);
context.MarkLabel(lblNoSat);
context.Copy(result, value);
context.MarkLabel(lblExit);
}
private static Operand EmitUnsigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
{
Operand tempD = context.AllocateLocal(OperandType.I32);
Operand tempN = context.ZeroExtend16(OperandType.I32, rn);
Operand tempM = context.ZeroExtend16(OperandType.I32, rm);
elementAction(tempD, tempN, tempM);
Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD);
tempN = context.ShiftRightUI(rn, Const(16));
tempM = context.ShiftRightUI(rm, Const(16));
elementAction(tempD, tempN, tempM);
return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16)));
}
private static Operand EmitSigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
{
return Emit8BitPair(context, rn, rm, elementAction, unsigned: false);
}
private static Operand EmitUnsigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
{
return Emit8BitPair(context, rn, rm, elementAction, unsigned: true);
}
private static Operand Emit8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction, bool unsigned)
{
Operand tempD = context.AllocateLocal(OperandType.I32);
Operand result = default;
for (int b = 0; b < 4; b++)
{
Operand nByte = b != 0 ? context.ShiftRightUI(rn, Const(b * 8)) : rn;
Operand mByte = b != 0 ? context.ShiftRightUI(rm, Const(b * 8)) : rm;
if (unsigned)
{
nByte = context.ZeroExtend8(OperandType.I32, nByte);
mByte = context.ZeroExtend8(OperandType.I32, mByte);
}
else
{
nByte = context.SignExtend8(OperandType.I32, nByte);
mByte = context.SignExtend8(OperandType.I32, mByte);
}
elementAction(tempD, nByte, mByte);
if (b == 0)
{
result = context.ZeroExtend8(OperandType.I32, tempD);
}
else if (b < 3)
{
result = context.BitwiseOr(result, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, tempD), Const(b * 8)));
}
else
{
result = context.BitwiseOr(result, context.ShiftLeft(tempD, Const(24)));
}
}
return result;
}
private static void EmitAluStore(ArmEmitterContext context, Operand value) private static void EmitAluStore(ArmEmitterContext context, Operand value)
{ {
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;

View File

@ -1246,6 +1246,33 @@ namespace ARMeilleure.Instructions
EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true); EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true);
} }
public static void Vqrdmulh(ArmEmitterContext context)
{
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
int eSize = 8 << op.Size;
EmitVectorBinaryOpI32(context, (op1, op2) =>
{
if (op.Size == 2)
{
op1 = context.SignExtend32(OperandType.I64, op1);
op2 = context.SignExtend32(OperandType.I64, op2);
}
Operand res = context.Multiply(op1, op2);
res = context.Add(res, Const(res.Type, 1L << (eSize - 2)));
res = context.ShiftRightSI(res, Const(eSize - 1));
res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true);
if (op.Size == 2)
{
res = context.ConvertI64ToI32(res);
}
return res;
}, signed: true);
}
public static void Vqsub(ArmEmitterContext context) public static void Vqsub(ArmEmitterContext context)
{ {
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;

View File

@ -191,6 +191,26 @@ namespace ARMeilleure.Instructions
context.Copy(GetVecA32(op.Qd), res); context.Copy(GetVecA32(op.Qd), res);
} }
public static void Vswp(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
if (op.Q)
{
Operand temp = context.Copy(GetVecA32(op.Qd));
context.Copy(GetVecA32(op.Qd), GetVecA32(op.Qm));
context.Copy(GetVecA32(op.Qm), temp);
}
else
{
Operand temp = ExtractScalar(context, OperandType.I64, op.Vd);
InsertScalar(context, op.Vd, ExtractScalar(context, OperandType.I64, op.Vm));
InsertScalar(context, op.Vm, temp);
}
}
public static void Vtbl(ArmEmitterContext context) public static void Vtbl(ArmEmitterContext context)
{ {
OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp; OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp;

View File

@ -130,6 +130,36 @@ namespace ARMeilleure.Instructions
EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift))); EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift)));
} }
public static void Vsli_I(ArmEmitterContext context)
{
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
int shift = op.Shift;
int eSize = 8 << op.Size;
ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL;
Operand res = GetVec(op.Qd);
int elems = op.GetBytesCount() >> op.Size;
for (int index = 0; index < elems; index++)
{
Operand me = EmitVectorExtractZx(context, op.Qm, op.Im + index, op.Size);
Operand neShifted = context.ShiftLeft(me, Const(shift));
Operand de = EmitVectorExtractZx(context, op.Qd, op.Id + index, op.Size);
Operand deMasked = context.BitwiseAnd(de, Const(mask));
Operand e = context.BitwiseOr(neShifted, deMasked);
res = EmitVectorInsert(context, res, e, op.Id + index, op.Size);
}
context.Copy(GetVec(op.Qd), res);
}
public static void Vsra(ArmEmitterContext context) public static void Vsra(ArmEmitterContext context)
{ {
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;

View File

@ -571,6 +571,10 @@ namespace ARMeilleure.Instructions
Umaal, Umaal,
Umlal, Umlal,
Umull, Umull,
Uqadd16,
Uqadd8,
Uqsub16,
Uqsub8,
Usat, Usat,
Usat16, Usat16,
Usub8, Usub8,
@ -645,6 +649,7 @@ namespace ARMeilleure.Instructions
Vqdmulh, Vqdmulh,
Vqmovn, Vqmovn,
Vqmovun, Vqmovun,
Vqrdmulh,
Vqrshrn, Vqrshrn,
Vqrshrun, Vqrshrun,
Vqshrn, Vqshrn,
@ -666,6 +671,7 @@ namespace ARMeilleure.Instructions
Vshll, Vshll,
Vshr, Vshr,
Vshrn, Vshrn,
Vsli,
Vst1, Vst1,
Vst2, Vst2,
Vst3, Vst3,
@ -682,6 +688,7 @@ namespace ARMeilleure.Instructions
Vsub, Vsub,
Vsubl, Vsubl,
Vsubw, Vsubw,
Vswp,
Vtbl, Vtbl,
Vtrn, Vtrn,
Vtst, Vtst,

View File

@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
private int[] _postOrderMap; private int[] _postOrderMap;
public int LocalsCount { get; private set; } public int LocalsCount { get; private set; }
public BasicBlock Entry { get; } public BasicBlock Entry { get; private set; }
public IntrusiveList<BasicBlock> Blocks { get; } public IntrusiveList<BasicBlock> Blocks { get; }
public BasicBlock[] PostOrderBlocks => _postOrderBlocks; public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
public int[] PostOrderMap => _postOrderMap; public int[] PostOrderMap => _postOrderMap;
@ -34,6 +34,15 @@ namespace ARMeilleure.Translation
return result; return result;
} }
public void UpdateEntry(BasicBlock newEntry)
{
newEntry.AddSuccessor(Entry);
Entry = newEntry;
Blocks.AddFirst(newEntry);
Update();
}
public void Update() public void Update()
{ {
RemoveUnreachableBlocks(Blocks); RemoveUnreachableBlocks(Blocks);

View File

@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 6634; //! To be incremented manually for each change to the ARMeilleure project. private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0"; private const string ActualDir = "0";
private const string BackupDir = "1"; private const string BackupDir = "1";

View File

@ -89,6 +89,17 @@ namespace ARMeilleure.Translation
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode) public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
{ {
if (cfg.Entry.Predecessors.Count != 0)
{
// We expect the entry block to have no predecessors.
// This is required because we have a implicit context load at the start of the function,
// but if there is a jump to the start of the function, the context load would trash the modified values.
// Here we insert a new entry block that will jump to the existing entry block.
BasicBlock newEntry = new BasicBlock(cfg.Blocks.Count);
cfg.UpdateEntry(newEntry);
}
// Compute local register inputs and outputs used inside blocks. // Compute local register inputs and outputs used inside blocks.
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count]; RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count]; RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count];
@ -201,7 +212,7 @@ namespace ARMeilleure.Translation
// The only block without any predecessor should be the entry block. // The only block without any predecessor should be the entry block.
// It always needs a context load as it is the first block to run. // It always needs a context load as it is the first block to run.
if (block.Predecessors.Count == 0 || hasContextLoad) if (block == cfg.Entry || hasContextLoad)
{ {
long vecMask = globalInputs[block.Index].VecMask; long vecMask = globalInputs[block.Index].VecMask;
long intMask = globalInputs[block.Index].IntMask; long intMask = globalInputs[block.Index].IntMask;

View File

@ -80,7 +80,10 @@ namespace ARMeilleure.Translation
return true; return true;
} }
Monitor.Wait(Sync); if (!_disposed)
{
Monitor.Wait(Sync);
}
} }
} }

View File

@ -89,9 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2
return; return;
} }
using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * _bytesPerFrame); using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * _bytesPerFrame);
Span<byte> samples = samplesOwner.Memory.Span; Span<byte> samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length); _ringBuffer.Read(samples, 0, samples.Length);

View File

@ -122,9 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
int channelCount = areas.Length; int channelCount = areas.Length;
using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * bytesPerFrame); using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * bytesPerFrame);
Span<byte> samples = samplesOwner.Memory.Span; Span<byte> samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length); _ringBuffer.Read(samples, 0, samples.Length);

View File

@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Backends.Common
private readonly object _lock = new(); private readonly object _lock = new();
private IMemoryOwner<byte> _bufferOwner; private MemoryOwner<byte> _bufferOwner;
private Memory<byte> _buffer; private Memory<byte> _buffer;
private int _size; private int _size;
private int _headOffset; private int _headOffset;
@ -24,7 +24,7 @@ namespace Ryujinx.Audio.Backends.Common
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment) public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
{ {
_bufferOwner = ByteMemoryPool.RentCleared(initialCapacity); _bufferOwner = MemoryOwner<byte>.RentCleared(initialCapacity);
_buffer = _bufferOwner.Memory; _buffer = _bufferOwner.Memory;
} }
@ -62,7 +62,7 @@ namespace Ryujinx.Audio.Backends.Common
private void SetCapacityLocked(int capacity) private void SetCapacityLocked(int capacity)
{ {
IMemoryOwner<byte> newBufferOwner = ByteMemoryPool.RentCleared(capacity); MemoryOwner<byte> newBufferOwner = MemoryOwner<byte>.RentCleared(capacity);
Memory<byte> newBuffer = newBufferOwner.Memory; Memory<byte> newBuffer = newBufferOwner.Memory;
if (_size > 0) if (_size > 0)

View File

@ -18,16 +18,12 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
if (version == 2) if (version == 2)
{ {
return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion2, return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, PerformanceDetailVersion2>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
PerformanceEntryVersion2,
PerformanceDetailVersion2>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
} }
if (version == 1) if (version == 1)
{ {
return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion1, return (ulong)PerformanceManagerGeneric<PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, PerformanceDetailVersion1>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
PerformanceEntryVersion1,
PerformanceDetailVersion1>.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter);
} }
throw new NotImplementedException($"Unknown Performance metrics data format version {version}"); throw new NotImplementedException($"Unknown Performance metrics data format version {version}");

View File

@ -234,7 +234,7 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
{ {
performanceEntry = null; performanceEntry = null;
if (_entryDetailIndex > MaxFrameDetailCount) if (_entryDetailIndex >= MaxFrameDetailCount)
{ {
return false; return false;
} }
@ -245,7 +245,7 @@ namespace Ryujinx.Audio.Renderer.Server.Performance
EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(), EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(),
}; };
uint baseEntryOffset = (uint)(Unsafe.SizeOf<THeader>() + GetEntriesSize() + Unsafe.SizeOf<IPerformanceDetailEntry>() * _entryDetailIndex); uint baseEntryOffset = (uint)(Unsafe.SizeOf<THeader>() + GetEntriesSize() + Unsafe.SizeOf<TEntryDetail>() * _entryDetailIndex);
ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex]; ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex];

View File

@ -1,13 +1,33 @@
using Ryujinx.Common.Utilities;
using System; using System;
namespace Ryujinx.Common.GraphicsDriver namespace Ryujinx.Common.GraphicsDriver
{ {
public static class DriverUtilities public static class DriverUtilities
{ {
private static void AddMesaFlags(string envVar, string newFlags)
{
string existingFlags = Environment.GetEnvironmentVariable(envVar);
string flags = existingFlags == null ? newFlags : $"{existingFlags},{newFlags}";
OsUtils.SetEnvironmentVariableNoCaching(envVar, flags);
}
public static void InitDriverConfig(bool oglThreading)
{
if (OperatingSystem.IsLinux())
{
AddMesaFlags("RADV_DEBUG", "nodcc");
}
ToggleOGLThreading(oglThreading);
}
public static void ToggleOGLThreading(bool enabled) public static void ToggleOGLThreading(bool enabled)
{ {
Environment.SetEnvironmentVariable("mesa_glthread", enabled.ToString().ToLower()); OsUtils.SetEnvironmentVariableNoCaching("mesa_glthread", enabled.ToString().ToLower());
Environment.SetEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0"); OsUtils.SetEnvironmentVariableNoCaching("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
try try
{ {

View File

@ -1,51 +0,0 @@
using System;
using System.Buffers;
using System.Threading;
namespace Ryujinx.Common.Memory
{
public partial class ByteMemoryPool
{
/// <summary>
/// Represents a <see cref="IMemoryOwner{Byte}"/> that wraps an array rented from
/// <see cref="ArrayPool{Byte}.Shared"/> and exposes it as <see cref="Memory{Byte}"/>
/// with a length of the requested size.
/// </summary>
private sealed class ByteMemoryPoolBuffer : IMemoryOwner<byte>
{
private byte[] _array;
private readonly int _length;
public ByteMemoryPoolBuffer(int length)
{
_array = ArrayPool<byte>.Shared.Rent(length);
_length = length;
}
/// <summary>
/// Returns a <see cref="Memory{Byte}"/> belonging to this owner.
/// </summary>
public Memory<byte> Memory
{
get
{
byte[] array = _array;
ObjectDisposedException.ThrowIf(array is null, this);
return new Memory<byte>(array, 0, _length);
}
}
public void Dispose()
{
var array = Interlocked.Exchange(ref _array, null);
if (array != null)
{
ArrayPool<byte>.Shared.Return(array);
}
}
}
}
}

View File

@ -1,106 +0,0 @@
using System;
using System.Buffers;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// Provides a pool of re-usable byte array instances.
/// </summary>
public static partial class ByteMemoryPool
{
/// <summary>
/// Returns the maximum buffer size supported by this pool.
/// </summary>
public static int MaxBufferSize => Array.MaxLength;
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer may contain data from a prior use.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static IMemoryOwner<byte> Rent(long length)
=> RentImpl(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer may contain data from a prior use.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static IMemoryOwner<byte> Rent(ulong length)
=> RentImpl(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer may contain data from a prior use.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static IMemoryOwner<byte> Rent(int length)
=> RentImpl(length);
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer's contents are cleared (set to all 0s) before returning.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static IMemoryOwner<byte> RentCleared(long length)
=> RentCleared(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer's contents are cleared (set to all 0s) before returning.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static IMemoryOwner<byte> RentCleared(ulong length)
=> RentCleared(checked((int)length));
/// <summary>
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
/// The buffer's contents are cleared (set to all 0s) before returning.
/// </summary>
/// <param name="length">The buffer's required length in bytes</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static IMemoryOwner<byte> RentCleared(int length)
{
var buffer = RentImpl(length);
buffer.Memory.Span.Clear();
return buffer;
}
/// <summary>
/// Copies <paramref name="buffer"/> into a newly rented byte memory buffer.
/// </summary>
/// <param name="buffer">The byte buffer to copy</param>
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory with <paramref name="buffer"/> copied to it</returns>
public static IMemoryOwner<byte> RentCopy(ReadOnlySpan<byte> buffer)
{
var copy = RentImpl(buffer.Length);
buffer.CopyTo(copy.Memory.Span);
return copy;
}
private static ByteMemoryPoolBuffer RentImpl(int length)
{
if ((uint)length > Array.MaxLength)
{
throw new ArgumentOutOfRangeException(nameof(length), length, null);
}
return new ByteMemoryPoolBuffer(length);
}
}
}

View File

@ -0,0 +1,140 @@
#nullable enable
using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// An <see cref="IMemoryOwner{T}"/> implementation with an embedded length and fast <see cref="Span{T}"/>
/// accessor, with memory allocated from <seealso cref="ArrayPool{T}.Shared"/>.
/// </summary>
/// <typeparam name="T">The type of item to store.</typeparam>
public sealed class MemoryOwner<T> : IMemoryOwner<T>
{
private readonly int _length;
private T[]? _array;
/// <summary>
/// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
private MemoryOwner(int length)
{
_length = length;
_array = ArrayPool<T>.Shared.Rent(length);
}
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Rent(int length) => new(length);
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length and the content cleared.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length and the content cleared</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> RentCleared(int length)
{
MemoryOwner<T> result = new(length);
result._array.AsSpan(0, length).Clear();
return result;
}
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the content copied from the specified buffer.
/// </summary>
/// <param name="buffer">The buffer to copy</param>
/// <returns>A <see cref="MemoryOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns>
public static MemoryOwner<T> RentCopy(ReadOnlySpan<T> buffer)
{
MemoryOwner<T> result = new(buffer.Length);
buffer.CopyTo(result._array);
return result;
}
/// <summary>
/// Gets the number of items in the current instance.
/// </summary>
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _length;
}
/// <inheritdoc/>
public Memory<T> Memory
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
T[]? array = _array;
if (array is null)
{
ThrowObjectDisposedException();
}
return new(array, 0, _length);
}
}
/// <summary>
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
/// </summary>
/// <remarks>
/// Uses a trick made possible by the .NET 6+ runtime array layout.
/// </remarks>
public Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
T[]? array = _array;
if (array is null)
{
ThrowObjectDisposedException();
}
ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(array);
return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
}
}
/// <inheritdoc/>
public void Dispose()
{
T[]? array = Interlocked.Exchange(ref _array, null);
if (array is not null)
{
ArrayPool<T>.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
}
}
/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> when <see cref="_array"/> is <see langword="null"/>.
/// </summary>
[DoesNotReturn]
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException(nameof(MemoryOwner<T>), "The buffer has already been disposed.");
}
}
}

View File

@ -0,0 +1,114 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// A stack-only type that rents a buffer of a specified length from <seealso cref="ArrayPool{T}.Shared"/>.
/// It does not implement <see cref="IDisposable"/> to avoid being boxed, but should still be disposed. This
/// is easy since C# 8, which allows use of C# `using` constructs on any type that has a public Dispose() method.
/// To keep this type simple, fast, and read-only, it does not check or guard against multiple disposals.
/// For all these reasons, all usage should be with a `using` block or statement.
/// </summary>
/// <typeparam name="T">The type of item to store.</typeparam>
public readonly ref struct SpanOwner<T>
{
private readonly int _length;
private readonly T[] _array;
/// <summary>
/// Initializes a new instance of the <see cref="SpanOwner{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
private SpanOwner(int length)
{
_length = length;
_array = ArrayPool<T>.Shared.Rent(length);
}
/// <summary>
/// Gets an empty <see cref="SpanOwner{T}"/> instance.
/// </summary>
public static SpanOwner<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(0);
}
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified length.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Rent(int length) => new(length);
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the length and the content cleared.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length and the content cleared</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> RentCleared(int length)
{
SpanOwner<T> result = new(length);
result._array.AsSpan(0, length).Clear();
return result;
}
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the content copied from the specified buffer.
/// </summary>
/// <param name="buffer">The buffer to copy</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns>
public static SpanOwner<T> RentCopy(ReadOnlySpan<T> buffer)
{
SpanOwner<T> result = new(buffer.Length);
buffer.CopyTo(result._array);
return result;
}
/// <summary>
/// Gets the number of items in the current instance
/// </summary>
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _length;
}
/// <summary>
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
/// </summary>
/// <remarks>
/// Uses a trick made possible by the .NET 6+ runtime array layout.
/// </remarks>
public Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(_array);
return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
}
}
/// <summary>
/// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
ArrayPool<T>.Shared.Return(_array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
}
}
}

View File

@ -1,6 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -42,14 +42,14 @@ namespace Ryujinx.Common
return StreamUtils.StreamToBytes(stream); return StreamUtils.StreamToBytes(stream);
} }
public static IMemoryOwner<byte> ReadFileToRentedMemory(string filename) public static MemoryOwner<byte> ReadFileToRentedMemory(string filename)
{ {
var (assembly, path) = ResolveManifestPath(filename); var (assembly, path) = ResolveManifestPath(filename);
return ReadFileToRentedMemory(assembly, path); return ReadFileToRentedMemory(assembly, path);
} }
public static IMemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename) public static MemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename)
{ {
using var stream = GetStream(assembly, filename); using var stream = GetStream(assembly, filename);

View File

@ -0,0 +1,24 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.Utilities
{
public partial class OsUtils
{
[LibraryImport("libc", SetLastError = true)]
private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite);
public static void SetEnvironmentVariableNoCaching(string key, string value)
{
// Set the value in the cached environment variables, too.
Environment.SetEnvironmentVariable(key, value);
if (!OperatingSystem.IsWindows())
{
int res = setenv(key, value, 1);
Debug.Assert(res != -1);
}
}
}
}

View File

@ -1,6 +1,5 @@
using Microsoft.IO; using Microsoft.IO;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using System.Buffers;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -16,7 +15,7 @@ namespace Ryujinx.Common.Utilities
return output.ToArray(); return output.ToArray();
} }
public static IMemoryOwner<byte> StreamToRentedMemory(Stream input) public static MemoryOwner<byte> StreamToRentedMemory(Stream input)
{ {
if (input is MemoryStream inputMemoryStream) if (input is MemoryStream inputMemoryStream)
{ {
@ -26,9 +25,9 @@ namespace Ryujinx.Common.Utilities
{ {
long bytesExpected = input.Length; long bytesExpected = input.Length;
IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(bytesExpected); MemoryOwner<byte> ownedMemory = MemoryOwner<byte>.Rent(checked((int)bytesExpected));
var destSpan = ownedMemory.Memory.Span; var destSpan = ownedMemory.Span;
int totalBytesRead = 0; int totalBytesRead = 0;
@ -66,14 +65,14 @@ namespace Ryujinx.Common.Utilities
return stream.ToArray(); return stream.ToArray();
} }
private static IMemoryOwner<byte> MemoryStreamToRentedMemory(MemoryStream input) private static MemoryOwner<byte> MemoryStreamToRentedMemory(MemoryStream input)
{ {
input.Position = 0; input.Position = 0;
IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(input.Length); MemoryOwner<byte> ownedMemory = MemoryOwner<byte>.Rent(checked((int)input.Length));
// Discard the return value because we assume reading a MemoryStream always succeeds completely. // Discard the return value because we assume reading a MemoryStream always succeeds completely.
_ = input.Read(ownedMemory.Memory.Span); _ = input.Read(ownedMemory.Span);
return ownedMemory; return ownedMemory;
} }

View File

@ -303,9 +303,9 @@ namespace Ryujinx.Cpu.Jit
} }
else else
{ {
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size); MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
Read(va, memoryOwner.Memory.Span); Read(va, memoryOwner.Span);
return new WritableRegion(this, va, memoryOwner); return new WritableRegion(this, va, memoryOwner);
} }

View File

@ -1,6 +1,5 @@
using Ryujinx.Cpu.LightningJit.CodeGen; using Ryujinx.Cpu.LightningJit.CodeGen;
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
using System.Diagnostics;
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
{ {

View File

@ -114,7 +114,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) => InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) =>
{ {
context.Arm64Assembler.Add(d, n, m); context.Arm64Assembler.Add(d, n, m);
EmitSaturateUnsignedRange(context, d, 16); EmitSaturateUqadd(context, d, 16);
}); });
} }
@ -123,7 +123,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) => InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) =>
{ {
context.Arm64Assembler.Add(d, n, m); context.Arm64Assembler.Add(d, n, m);
EmitSaturateUnsignedRange(context, d, 8); EmitSaturateUqadd(context, d, 8);
}); });
} }
@ -140,7 +140,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
context.Arm64Assembler.Add(d, n, m); context.Arm64Assembler.Add(d, n, m);
} }
EmitSaturateUnsignedRange(context, d, 16); EmitSaturateUq(context, d, 16, e == 0);
}); });
} }
@ -157,25 +157,25 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
context.Arm64Assembler.Sub(d, n, m); context.Arm64Assembler.Sub(d, n, m);
} }
EmitSaturateUnsignedRange(context, d, 16); EmitSaturateUq(context, d, 16, e != 0);
}); });
} }
public static void Uqsub16(CodeGenContext context, uint rd, uint rn, uint rm) public static void Uqsub16(CodeGenContext context, uint rd, uint rn, uint rm)
{ {
InstEmitCommon.EmitSigned16BitPair(context, rd, rn, rm, (d, n, m) => InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) =>
{ {
context.Arm64Assembler.Sub(d, n, m); context.Arm64Assembler.Sub(d, n, m);
EmitSaturateUnsignedRange(context, d, 16); EmitSaturateUqsub(context, d, 16);
}); });
} }
public static void Uqsub8(CodeGenContext context, uint rd, uint rn, uint rm) public static void Uqsub8(CodeGenContext context, uint rd, uint rn, uint rm)
{ {
InstEmitCommon.EmitSigned8BitPair(context, rd, rn, rm, (d, n, m) => InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) =>
{ {
context.Arm64Assembler.Sub(d, n, m); context.Arm64Assembler.Sub(d, n, m);
EmitSaturateUnsignedRange(context, d, 8); EmitSaturateUqsub(context, d, 8);
}); });
} }
@ -358,7 +358,17 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
} }
} }
private static void EmitSaturateUnsignedRange(CodeGenContext context, Operand value, uint saturateTo) private static void EmitSaturateUqadd(CodeGenContext context, Operand value, uint saturateTo)
{
EmitSaturateUq(context, value, saturateTo, isSub: false);
}
private static void EmitSaturateUqsub(CodeGenContext context, Operand value, uint saturateTo)
{
EmitSaturateUq(context, value, saturateTo, isSub: true);
}
private static void EmitSaturateUq(CodeGenContext context, Operand value, uint saturateTo, bool isSub)
{ {
Debug.Assert(saturateTo <= 32); Debug.Assert(saturateTo <= 32);
@ -379,7 +389,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
return; return;
} }
context.Arm64Assembler.Lsr(tempRegister.Operand, value, InstEmitCommon.Const(32 - (int)saturateTo)); context.Arm64Assembler.Lsr(tempRegister.Operand, value, InstEmitCommon.Const((int)saturateTo));
int branchIndex = context.CodeWriter.InstructionPointer; int branchIndex = context.CodeWriter.InstructionPointer;
@ -387,7 +397,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
context.Arm64Assembler.Cbz(tempRegister.Operand, 0); context.Arm64Assembler.Cbz(tempRegister.Operand, 0);
// Saturate. // Saturate.
context.Arm64Assembler.Mov(value, uint.MaxValue >> (32 - (int)saturateTo)); context.Arm64Assembler.Mov(value, isSub ? 0u : uint.MaxValue >> (32 - (int)saturateTo));
int delta = context.CodeWriter.InstructionPointer - branchIndex; int delta = context.CodeWriter.InstructionPointer - branchIndex;
context.CodeWriter.WriteInstructionAt(branchIndex, context.CodeWriter.ReadInstructionAt(branchIndex) | (uint)((delta & 0x7ffff) << 5)); context.CodeWriter.WriteInstructionAt(branchIndex, context.CodeWriter.ReadInstructionAt(branchIndex) | (uint)((delta & 0x7ffff) << 5));

View File

@ -1,7 +1,6 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -145,9 +144,9 @@ namespace Ryujinx.Graphics.Device
} }
else else
{ {
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size); MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
GetSpan(va, size).CopyTo(memoryOwner.Memory.Span); ReadImpl(va, memoryOwner.Span);
return new WritableRegion(this, va, memoryOwner, tracked: true); return new WritableRegion(this, va, memoryOwner, tracked: true);
} }

View File

@ -39,7 +39,10 @@ namespace Ryujinx.Graphics.Device
{ {
var field = fields[fieldIndex]; var field = fields[fieldIndex];
int sizeOfField = SizeCalculator.SizeOf(field.FieldType); var currentFieldOffset = (int)Marshal.OffsetOf<TState>(field.Name);
var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf<TState>() : (int)Marshal.OffsetOf<TState>(fields[fieldIndex + 1].Name);
int sizeOfField = nextFieldOffset - currentFieldOffset;
for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4) for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4)
{ {

View File

@ -1,63 +0,0 @@
using System;
using System.Reflection;
namespace Ryujinx.Graphics.Device
{
public static class SizeCalculator
{
public static int SizeOf(Type type)
{
// Is type a enum type?
if (type.IsEnum)
{
type = type.GetEnumUnderlyingType();
}
// Is type a pointer type?
if (type.IsPointer || type == typeof(IntPtr) || type == typeof(UIntPtr))
{
return IntPtr.Size;
}
// Is type a struct type?
if (type.IsValueType && !type.IsPrimitive)
{
// Check if the struct has a explicit size, if so, return that.
if (type.StructLayoutAttribute.Size != 0)
{
return type.StructLayoutAttribute.Size;
}
// Otherwise we calculate the sum of the sizes of all fields.
int size = 0;
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
for (int fieldIndex = 0; fieldIndex < fields.Length; fieldIndex++)
{
size += SizeOf(fields[fieldIndex].FieldType);
}
return size;
}
// Primitive types.
return (Type.GetTypeCode(type)) switch
{
TypeCode.SByte => sizeof(sbyte),
TypeCode.Byte => sizeof(byte),
TypeCode.Int16 => sizeof(short),
TypeCode.UInt16 => sizeof(ushort),
TypeCode.Int32 => sizeof(int),
TypeCode.UInt32 => sizeof(uint),
TypeCode.Int64 => sizeof(long),
TypeCode.UInt64 => sizeof(ulong),
TypeCode.Char => sizeof(char),
TypeCode.Single => sizeof(float),
TypeCode.Double => sizeof(double),
TypeCode.Decimal => sizeof(decimal),
TypeCode.Boolean => sizeof(bool),
_ => throw new ArgumentException($"Length for type \"{type.Name}\" is unknown."),
};
}
}
}

View File

@ -6,8 +6,13 @@ namespace Ryujinx.Graphics.GAL
public enum BufferAccess public enum BufferAccess
{ {
Default = 0, Default = 0,
FlushPersistent = 1 << 0, HostMemory = 1,
Stream = 1 << 1, DeviceMemory = 2,
SparseCompatible = 1 << 2, DeviceMemoryMapped = 3,
MemoryTypeMask = 0xf,
Stream = 1 << 4,
SparseCompatible = 1 << 5,
} }
} }

View File

@ -6,6 +6,7 @@ namespace Ryujinx.Graphics.GAL
{ {
public readonly TargetApi Api; public readonly TargetApi Api;
public readonly string VendorName; public readonly string VendorName;
public readonly SystemMemoryType MemoryType;
public readonly bool HasFrontFacingBug; public readonly bool HasFrontFacingBug;
public readonly bool HasVectorIndexingBug; public readonly bool HasVectorIndexingBug;
@ -50,6 +51,13 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsIndirectParameters; public readonly bool SupportsIndirectParameters;
public readonly bool SupportsDepthClipControl; public readonly bool SupportsDepthClipControl;
public readonly int UniformBufferSetIndex;
public readonly int StorageBufferSetIndex;
public readonly int TextureSetIndex;
public readonly int ImageSetIndex;
public readonly int ExtraSetBaseIndex;
public readonly int MaximumExtraSets;
public readonly uint MaximumUniformBuffersPerStage; public readonly uint MaximumUniformBuffersPerStage;
public readonly uint MaximumStorageBuffersPerStage; public readonly uint MaximumStorageBuffersPerStage;
public readonly uint MaximumTexturesPerStage; public readonly uint MaximumTexturesPerStage;
@ -66,6 +74,7 @@ namespace Ryujinx.Graphics.GAL
public Capabilities( public Capabilities(
TargetApi api, TargetApi api,
string vendorName, string vendorName,
SystemMemoryType memoryType,
bool hasFrontFacingBug, bool hasFrontFacingBug,
bool hasVectorIndexingBug, bool hasVectorIndexingBug,
bool needsFragmentOutputSpecialization, bool needsFragmentOutputSpecialization,
@ -107,6 +116,12 @@ namespace Ryujinx.Graphics.GAL
bool supportsViewportSwizzle, bool supportsViewportSwizzle,
bool supportsIndirectParameters, bool supportsIndirectParameters,
bool supportsDepthClipControl, bool supportsDepthClipControl,
int uniformBufferSetIndex,
int storageBufferSetIndex,
int textureSetIndex,
int imageSetIndex,
int extraSetBaseIndex,
int maximumExtraSets,
uint maximumUniformBuffersPerStage, uint maximumUniformBuffersPerStage,
uint maximumStorageBuffersPerStage, uint maximumStorageBuffersPerStage,
uint maximumTexturesPerStage, uint maximumTexturesPerStage,
@ -120,6 +135,7 @@ namespace Ryujinx.Graphics.GAL
{ {
Api = api; Api = api;
VendorName = vendorName; VendorName = vendorName;
MemoryType = memoryType;
HasFrontFacingBug = hasFrontFacingBug; HasFrontFacingBug = hasFrontFacingBug;
HasVectorIndexingBug = hasVectorIndexingBug; HasVectorIndexingBug = hasVectorIndexingBug;
NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization; NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization;
@ -161,6 +177,12 @@ namespace Ryujinx.Graphics.GAL
SupportsViewportSwizzle = supportsViewportSwizzle; SupportsViewportSwizzle = supportsViewportSwizzle;
SupportsIndirectParameters = supportsIndirectParameters; SupportsIndirectParameters = supportsIndirectParameters;
SupportsDepthClipControl = supportsDepthClipControl; SupportsDepthClipControl = supportsDepthClipControl;
UniformBufferSetIndex = uniformBufferSetIndex;
StorageBufferSetIndex = storageBufferSetIndex;
TextureSetIndex = textureSetIndex;
ImageSetIndex = imageSetIndex;
ExtraSetBaseIndex = extraSetBaseIndex;
MaximumExtraSets = maximumExtraSets;
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage; MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage; MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
MaximumTexturesPerStage = maximumTexturesPerStage; MaximumTexturesPerStage = maximumTexturesPerStage;

View File

@ -711,5 +711,36 @@ namespace Ryujinx.Graphics.GAL
{ {
return format.IsUint() || format.IsSint(); return format.IsUint() || format.IsSint();
} }
/// <summary>
/// Checks if the texture format is a float or sRGB color format.
/// </summary>
/// <remarks>
/// Does not include normalized, compressed or depth formats.
/// Float and sRGB formats do not participate in logical operations.
/// </remarks>
/// <param name="format">Texture format</param>
/// <returns>True if the format is a float or sRGB color format, false otherwise</returns>
public static bool IsFloatOrSrgb(this Format format)
{
switch (format)
{
case Format.R8G8B8A8Srgb:
case Format.B8G8R8A8Srgb:
case Format.R16Float:
case Format.R16G16Float:
case Format.R16G16B16Float:
case Format.R16G16B16A16Float:
case Format.R32Float:
case Format.R32G32Float:
case Format.R32G32B32Float:
case Format.R32G32B32A32Float:
case Format.R11G11B10Float:
case Format.R9G9B9E5Float:
return true;
}
return false;
}
} }
} }

View File

@ -1,6 +1,8 @@
using System;
namespace Ryujinx.Graphics.GAL namespace Ryujinx.Graphics.GAL
{ {
public interface IImageArray public interface IImageArray : IDisposable
{ {
void SetFormats(int index, Format[] imageFormats); void SetFormats(int index, Format[] imageFormats);
void SetImages(int index, ITexture[] images); void SetImages(int index, ITexture[] images);

View File

@ -60,6 +60,7 @@ namespace Ryujinx.Graphics.GAL
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat); void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
void SetImageArray(ShaderStage stage, int binding, IImageArray array); void SetImageArray(ShaderStage stage, int binding, IImageArray array);
void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array);
void SetLineParameters(float width, bool smooth); void SetLineParameters(float width, bool smooth);
@ -91,6 +92,7 @@ namespace Ryujinx.Graphics.GAL
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler); void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
void SetTextureArray(ShaderStage stage, int binding, ITextureArray array); void SetTextureArray(ShaderStage stage, int binding, ITextureArray array);
void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array);
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers); void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers); void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);

View File

@ -17,7 +17,6 @@ namespace Ryujinx.Graphics.GAL
void BackgroundContextAction(Action action, bool alwaysBackground = false); void BackgroundContextAction(Action action, bool alwaysBackground = false);
BufferHandle CreateBuffer(int size, BufferAccess access = BufferAccess.Default); BufferHandle CreateBuffer(int size, BufferAccess access = BufferAccess.Default);
BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint);
BufferHandle CreateBuffer(nint pointer, int size); BufferHandle CreateBuffer(nint pointer, int size);
BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers); BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers);

View File

@ -1,6 +1,8 @@
using System;
namespace Ryujinx.Graphics.GAL namespace Ryujinx.Graphics.GAL
{ {
public interface ITextureArray public interface ITextureArray : IDisposable
{ {
void SetSamplers(int index, ISampler[] samplers); void SetSamplers(int index, ISampler[] samplers);
void SetTextures(int index, ITexture[] textures); void SetTextures(int index, ITexture[] textures);

View File

@ -44,7 +44,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
} }
Register<ActionCommand>(CommandType.Action); Register<ActionCommand>(CommandType.Action);
Register<CreateBufferCommand>(CommandType.CreateBuffer);
Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess); Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess);
Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse); Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse);
Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer); Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer);
@ -67,6 +66,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose); Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush); Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
Register<ImageArrayDisposeCommand>(CommandType.ImageArrayDispose);
Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats); Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats);
Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages); Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages);
@ -89,6 +89,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion); Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
Register<TextureSetStorageCommand>(CommandType.TextureSetStorage); Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
Register<TextureArrayDisposeCommand>(CommandType.TextureArrayDispose);
Register<TextureArraySetSamplersCommand>(CommandType.TextureArraySetSamplers); Register<TextureArraySetSamplersCommand>(CommandType.TextureArraySetSamplers);
Register<TextureArraySetTexturesCommand>(CommandType.TextureArraySetTextures); Register<TextureArraySetTexturesCommand>(CommandType.TextureArraySetTextures);
@ -125,6 +126,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers); Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers);
Register<SetImageCommand>(CommandType.SetImage); Register<SetImageCommand>(CommandType.SetImage);
Register<SetImageArrayCommand>(CommandType.SetImageArray); Register<SetImageArrayCommand>(CommandType.SetImageArray);
Register<SetImageArraySeparateCommand>(CommandType.SetImageArraySeparate);
Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer); Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer);
Register<SetLineParametersCommand>(CommandType.SetLineParameters); Register<SetLineParametersCommand>(CommandType.SetLineParameters);
Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState); Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState);
@ -142,6 +144,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<SetStencilTestCommand>(CommandType.SetStencilTest); Register<SetStencilTestCommand>(CommandType.SetStencilTest);
Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler); Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler);
Register<SetTextureArrayCommand>(CommandType.SetTextureArray); Register<SetTextureArrayCommand>(CommandType.SetTextureArray);
Register<SetTextureArraySeparateCommand>(CommandType.SetTextureArraySeparate);
Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance); Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance);
Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs); Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs);
Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers); Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers);

View File

@ -3,7 +3,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
enum CommandType : byte enum CommandType : byte
{ {
Action, Action,
CreateBuffer,
CreateBufferAccess, CreateBufferAccess,
CreateBufferSparse, CreateBufferSparse,
CreateHostBuffer, CreateHostBuffer,
@ -27,6 +26,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
CounterEventDispose, CounterEventDispose,
CounterEventFlush, CounterEventFlush,
ImageArrayDispose,
ImageArraySetFormats, ImageArraySetFormats,
ImageArraySetImages, ImageArraySetImages,
@ -49,6 +49,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
TextureSetDataSliceRegion, TextureSetDataSliceRegion,
TextureSetStorage, TextureSetStorage,
TextureArrayDispose,
TextureArraySetSamplers, TextureArraySetSamplers,
TextureArraySetTextures, TextureArraySetTextures,
@ -85,6 +86,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetUniformBuffers, SetUniformBuffers,
SetImage, SetImage,
SetImageArray, SetImageArray,
SetImageArraySeparate,
SetIndexBuffer, SetIndexBuffer,
SetLineParameters, SetLineParameters,
SetLogicOpState, SetLogicOpState,
@ -102,6 +104,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetStencilTest, SetStencilTest,
SetTextureAndSampler, SetTextureAndSampler,
SetTextureArray, SetTextureArray,
SetTextureArraySeparate,
SetUserClipDistance, SetUserClipDistance,
SetVertexAttribs, SetVertexAttribs,
SetVertexBuffers, SetVertexBuffers,

View File

@ -0,0 +1,21 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
{
struct ImageArrayDisposeCommand : IGALCommand, IGALCommand<ImageArrayDisposeCommand>
{
public readonly CommandType CommandType => CommandType.ImageArrayDispose;
private TableRef<ThreadedImageArray> _imageArray;
public void Set(TableRef<ThreadedImageArray> imageArray)
{
_imageArray = imageArray;
}
public static void Run(ref ImageArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
command._imageArray.Get(threaded).Base.Dispose();
}
}
}

View File

@ -1,31 +0,0 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{
struct CreateBufferCommand : IGALCommand, IGALCommand<CreateBufferCommand>
{
public readonly CommandType CommandType => CommandType.CreateBuffer;
private BufferHandle _threadedHandle;
private int _size;
private BufferAccess _access;
private BufferHandle _storageHint;
public void Set(BufferHandle threadedHandle, int size, BufferAccess access, BufferHandle storageHint)
{
_threadedHandle = threadedHandle;
_size = size;
_access = access;
_storageHint = storageHint;
}
public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
BufferHandle hint = BufferHandle.Null;
if (command._storageHint != BufferHandle.Null)
{
hint = threaded.Buffers.MapBuffer(command._storageHint);
}
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, command._access, hint));
}
}
}

View File

@ -0,0 +1,26 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetImageArraySeparateCommand : IGALCommand, IGALCommand<SetImageArraySeparateCommand>
{
public readonly CommandType CommandType => CommandType.SetImageArraySeparate;
private ShaderStage _stage;
private int _setIndex;
private TableRef<IImageArray> _array;
public void Set(ShaderStage stage, int setIndex, TableRef<IImageArray> array)
{
_stage = stage;
_setIndex = setIndex;
_array = array;
}
public static void Run(ref SetImageArraySeparateCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetImageArraySeparate(command._stage, command._setIndex, command._array.GetAs<ThreadedImageArray>(threaded)?.Base);
}
}
}

View File

@ -0,0 +1,26 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetTextureArraySeparateCommand : IGALCommand, IGALCommand<SetTextureArraySeparateCommand>
{
public readonly CommandType CommandType => CommandType.SetTextureArraySeparate;
private ShaderStage _stage;
private int _setIndex;
private TableRef<ITextureArray> _array;
public void Set(ShaderStage stage, int setIndex, TableRef<ITextureArray> array)
{
_stage = stage;
_setIndex = setIndex;
_array = array;
}
public static void Run(ref SetTextureArraySeparateCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetTextureArraySeparate(command._stage, command._setIndex, command._array.GetAs<ThreadedTextureArray>(threaded)?.Base);
}
}
}

View File

@ -0,0 +1,21 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray
{
struct TextureArrayDisposeCommand : IGALCommand, IGALCommand<TextureArrayDisposeCommand>
{
public readonly CommandType CommandType => CommandType.TextureArrayDispose;
private TableRef<ThreadedTextureArray> _textureArray;
public void Set(TableRef<ThreadedTextureArray> textureArray)
{
_textureArray = textureArray;
}
public static void Run(ref TextureArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
command._textureArray.Get(threaded).Base.Dispose();
}
}
}

View File

@ -21,6 +21,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return new TableRef<T>(_renderer, reference); return new TableRef<T>(_renderer, reference);
} }
public void Dispose()
{
_renderer.New<ImageArrayDisposeCommand>().Set(Ref(this));
_renderer.QueueCommand();
}
public void SetFormats(int index, Format[] imageFormats) public void SetFormats(int index, Format[] imageFormats)
{ {
_renderer.New<ImageArraySetFormatsCommand>().Set(Ref(this), index, Ref(imageFormats)); _renderer.New<ImageArraySetFormatsCommand>().Set(Ref(this), index, Ref(imageFormats));

View File

@ -22,6 +22,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return new TableRef<T>(_renderer, reference); return new TableRef<T>(_renderer, reference);
} }
public void Dispose()
{
_renderer.New<TextureArrayDisposeCommand>().Set(Ref(this));
_renderer.QueueCommand();
}
public void SetSamplers(int index, ISampler[] samplers) public void SetSamplers(int index, ISampler[] samplers)
{ {
_renderer.New<TextureArraySetSamplersCommand>().Set(Ref(this), index, Ref(samplers.ToArray())); _renderer.New<TextureArraySetSamplersCommand>().Set(Ref(this), index, Ref(samplers.ToArray()));

View File

@ -189,6 +189,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array)
{
_renderer.New<SetImageArraySeparateCommand>().Set(stage, setIndex, Ref(array));
_renderer.QueueCommand();
}
public void SetIndexBuffer(BufferRange buffer, IndexType type) public void SetIndexBuffer(BufferRange buffer, IndexType type)
{ {
_renderer.New<SetIndexBufferCommand>().Set(buffer, type); _renderer.New<SetIndexBufferCommand>().Set(buffer, type);
@ -297,6 +303,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array)
{
_renderer.New<SetTextureArraySeparateCommand>().Set(stage, setIndex, Ref(array));
_renderer.QueueCommand();
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers) public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{ {
_renderer.New<SetTransformFeedbackBuffersCommand>().Set(_renderer.CopySpan(buffers)); _renderer.New<SetTransformFeedbackBuffersCommand>().Set(_renderer.CopySpan(buffers));

View File

@ -272,15 +272,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return handle; return handle;
} }
public BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint)
{
BufferHandle handle = Buffers.CreateBufferHandle();
New<CreateBufferCommand>().Set(handle, size, access, storageHint);
QueueCommand();
return handle;
}
public BufferHandle CreateBuffer(nint pointer, int size) public BufferHandle CreateBuffer(nint pointer, int size)
{ {
BufferHandle handle = Buffers.CreateBufferHandle(); BufferHandle handle = Buffers.CreateBufferHandle();

View File

@ -74,13 +74,15 @@ namespace Ryujinx.Graphics.GAL
public int ArrayLength { get; } public int ArrayLength { get; }
public ResourceType Type { get; } public ResourceType Type { get; }
public ResourceStages Stages { get; } public ResourceStages Stages { get; }
public bool Write { get; }
public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages) public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages, bool write)
{ {
Binding = binding; Binding = binding;
ArrayLength = arrayLength; ArrayLength = arrayLength;
Type = type; Type = type;
Stages = stages; Stages = stages;
Write = write;
} }
public override int GetHashCode() public override int GetHashCode()

View File

@ -0,0 +1,29 @@
namespace Ryujinx.Graphics.GAL
{
public enum SystemMemoryType
{
/// <summary>
/// The backend manages the ownership of memory. This mode never supports host imported memory.
/// </summary>
BackendManaged,
/// <summary>
/// Device memory has similar performance to host memory, usually because it's shared between CPU/GPU.
/// Use host memory whenever possible.
/// </summary>
UnifiedMemory,
/// <summary>
/// GPU storage to host memory goes though a slow interconnect, but it would still be preferable to use it if the data is flushed back often.
/// Assumes constant buffer access to host memory is rather fast.
/// </summary>
DedicatedMemory,
/// <summary>
/// GPU storage to host memory goes though a slow interconnect, that is very slow when doing access from storage.
/// When frequently accessed, copy buffers to host memory using DMA.
/// Assumes constant buffer access to host memory is rather fast.
/// </summary>
DedicatedMemorySlowStorage
}
}

View File

@ -1,6 +1,5 @@
using Ryujinx.Common; using Ryujinx.Common;
using System; using System;
using System.Numerics;
namespace Ryujinx.Graphics.GAL namespace Ryujinx.Graphics.GAL
{ {
@ -113,25 +112,6 @@ namespace Ryujinx.Graphics.GAL
return 1; return 1;
} }
public int GetLevelsClamped()
{
int maxSize = Width;
if (Target != Target.Texture1D &&
Target != Target.Texture1DArray)
{
maxSize = Math.Max(maxSize, Height);
}
if (Target == Target.Texture3D)
{
maxSize = Math.Max(maxSize, Depth);
}
int maxLevels = BitOperations.Log2((uint)maxSize) + 1;
return Math.Min(Levels, maxLevels);
}
private static int GetLevelSize(int size, int level) private static int GetLevelSize(int size, int level)
{ {
return Math.Max(1, size >> level); return Math.Max(1, size >> level);

View File

@ -199,7 +199,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
if (target != null) if (target != null)
{ {
target.SynchronizeMemory(); target.SynchronizeMemory();
var dataCopy = ByteMemoryPool.RentCopy(data); var dataCopy = MemoryOwner<byte>.RentCopy(data);
target.SetData(dataCopy, 0, 0, new GAL.Rectangle<int>(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount)); target.SetData(dataCopy, 0, 0, new GAL.Rectangle<int>(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount));
target.SignalModified(); target.SignalModified();

View File

@ -5,6 +5,7 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.GPFifo; using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Engine.Threed; using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -495,8 +496,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride; ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize); MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4); MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
_processor.ThreedClass.DrawIndirect( _processor.ThreedClass.DrawIndirect(
topology, topology,

View File

@ -438,7 +438,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
ReadOnlySpan<byte> dataBytes = MemoryMarshal.Cast<int, byte>(data); ReadOnlySpan<byte> dataBytes = MemoryMarshal.Cast<int, byte>(data);
BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length); BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length, BufferAccess.DeviceMemory);
_context.Renderer.SetBufferData(buffer, 0, dataBytes); _context.Renderer.SetBufferData(buffer, 0, dataBytes);
return new IndexBuffer(buffer, count, dataBytes.Length); return new IndexBuffer(buffer, count, dataBytes.Length);
@ -529,7 +529,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
{ {
if (_dummyBuffer == BufferHandle.Null) if (_dummyBuffer == BufferHandle.Null)
{ {
_dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize); _dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize, BufferAccess.DeviceMemory);
_context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0); _context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0);
} }
@ -550,7 +550,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
_context.Renderer.DeleteBuffer(_sequentialIndexBuffer); _context.Renderer.DeleteBuffer(_sequentialIndexBuffer);
} }
_sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint)); _sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint), BufferAccess.DeviceMemory);
_sequentialIndexBufferCount = count; _sequentialIndexBufferCount = count;
Span<int> data = new int[count]; Span<int> data = new int[count];
@ -583,7 +583,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
_context.Renderer.DeleteBuffer(buffer.Handle); _context.Renderer.DeleteBuffer(buffer.Handle);
} }
buffer.Handle = _context.Renderer.CreateBuffer(newSize); buffer.Handle = _context.Renderer.CreateBuffer(newSize, BufferAccess.DeviceMemory);
buffer.Size = newSize; buffer.Size = newSize;
} }

View File

@ -3,6 +3,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation; using Ryujinx.Graphics.Shader.Translation;
@ -370,7 +371,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
{ {
var memoryManager = _channel.MemoryManager; var memoryManager = _channel.MemoryManager;
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size)); BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer);
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format); ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
bufferTexture.SetStorage(range); bufferTexture.SetStorage(range);
@ -412,7 +413,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
var memoryManager = _channel.MemoryManager; var memoryManager = _channel.MemoryManager;
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1); ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign)); BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(
memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign),
BufferStage.IndexBuffer);
misalignedOffset = (int)misalign >> shift; misalignedOffset = (int)misalign >> shift;
SetIndexBufferTexture(reservations, range, format); SetIndexBufferTexture(reservations, range, format);

View File

@ -684,8 +684,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (hasCount) if (hasCount)
{ {
var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange); var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange); var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
if (indexed) if (indexed)
{ {
@ -698,7 +698,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
} }
else else
{ {
var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange); var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
if (indexed) if (indexed)
{ {

View File

@ -79,7 +79,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{ {
var field = fields[fieldIndex]; var field = fields[fieldIndex];
int sizeOfField = SizeCalculator.SizeOf(field.FieldType); var currentFieldOffset = (int)Marshal.OffsetOf<TState>(field.Name);
var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf<TState>() : (int)Marshal.OffsetOf<TState>(fields[fieldIndex + 1].Name);
int sizeOfField = nextFieldOffset - currentFieldOffset;
if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex)) if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex))
{ {

View File

@ -415,7 +415,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
#pragma warning disable CS0649 // Field is never assigned to #pragma warning disable CS0649 // Field is never assigned to
public int Width; public int Width;
public int Height; public int Height;
public int Depth; public ushort Depth;
public ushort Flags;
public readonly bool UnpackIsLayered()
{
return (Flags & 1) == 0;
}
#pragma warning restore CS0649 #pragma warning restore CS0649
} }

View File

@ -393,17 +393,18 @@ namespace Ryujinx.Graphics.Gpu
if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0)) if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0))
{ {
Renderer.CreateSync(SyncNumber, strict);
foreach (var action in SyncActions) foreach (var action in SyncActions)
{ {
action.SyncPreAction(syncpoint); action.SyncPreAction(syncpoint);
} }
foreach (var action in SyncpointActions) foreach (var action in SyncpointActions)
{ {
action.SyncPreAction(syncpoint); action.SyncPreAction(syncpoint);
} }
Renderer.CreateSync(SyncNumber, strict);
SyncNumber++; SyncNumber++;
SyncActions.RemoveAll(action => action.SyncAction(syncpoint)); SyncActions.RemoveAll(action => action.SyncAction(syncpoint));

View File

@ -1,4 +1,5 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
@ -805,7 +806,7 @@ namespace Ryujinx.Graphics.Gpu.Image
sliceDepth, sliceDepth,
levels, levels,
layers, layers,
out IMemoryOwner<byte> decoded)) out MemoryOwner<byte> decoded))
{ {
string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}";

View File

@ -19,6 +19,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public Format Format { get; } public Format Format { get; }
/// <summary>
/// Shader texture host set index.
/// </summary>
public int Set { get; }
/// <summary> /// <summary>
/// Shader texture host binding point. /// Shader texture host binding point.
/// </summary> /// </summary>
@ -54,15 +59,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
/// <param name="target">The shader sampler target type</param> /// <param name="target">The shader sampler target type</param>
/// <param name="format">Format of the image as declared on the shader</param> /// <param name="format">Format of the image as declared on the shader</param>
/// <param name="set">Shader texture host set index</param>
/// <param name="binding">The shader texture binding point</param> /// <param name="binding">The shader texture binding point</param>
/// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param> /// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param> /// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param> /// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param> /// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
public TextureBindingInfo(Target target, Format format, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) public TextureBindingInfo(Target target, Format format, int set, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags)
{ {
Target = target; Target = target;
Format = format; Format = format;
Set = set;
Binding = binding; Binding = binding;
ArrayLength = arrayLength; ArrayLength = arrayLength;
CbufSlot = cbufSlot; CbufSlot = cbufSlot;
@ -74,6 +81,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Constructs the texture binding information structure. /// Constructs the texture binding information structure.
/// </summary> /// </summary>
/// <param name="target">The shader sampler target type</param> /// <param name="target">The shader sampler target type</param>
/// <param name="set">Shader texture host set index</param>
/// <param name="binding">The shader texture binding point</param> /// <param name="binding">The shader texture binding point</param>
/// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param> /// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param> /// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
@ -82,12 +90,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="isSamplerOnly">Indicates that the binding is for a sampler</param> /// <param name="isSamplerOnly">Indicates that the binding is for a sampler</param>
public TextureBindingInfo( public TextureBindingInfo(
Target target, Target target,
int set,
int binding, int binding,
int arrayLength, int arrayLength,
int cbufSlot, int cbufSlot,
int handle, int handle,
TextureUsageFlags flags, TextureUsageFlags flags,
bool isSamplerOnly) : this(target, 0, binding, arrayLength, cbufSlot, handle, flags) bool isSamplerOnly) : this(target, 0, set, binding, arrayLength, cbufSlot, handle, flags)
{ {
IsSamplerOnly = isSamplerOnly; IsSamplerOnly = isSamplerOnly;
} }

View File

@ -340,7 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>True if any used entries of the pool might have been modified, false otherwise</returns> /// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
public bool SamplerPoolModified() public bool SamplerPoolModified()
{ {
return SamplerPool.WasModified(ref _samplerPoolSequence); return SamplerPool != null && SamplerPool.WasModified(ref _samplerPoolSequence);
} }
} }
@ -516,12 +516,15 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
// Check if any of our cached samplers changed on the pool. // Check if any of our cached samplers changed on the pool.
foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds) if (SamplerPool != null)
{ {
if (SamplerPool.GetCachedItem(samplerId) != sampler || foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds)
(sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor)))
{ {
return true; if (SamplerPool.GetCachedItem(samplerId) != sampler ||
(sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor)))
{
return true;
}
} }
} }
@ -566,7 +569,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int stageIndex, int stageIndex,
int textureBufferIndex, int textureBufferIndex,
SamplerIndex samplerIndex, SamplerIndex samplerIndex,
TextureBindingInfo bindingInfo) in TextureBindingInfo bindingInfo)
{ {
Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo); Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo);
} }
@ -579,7 +582,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="stageIndex">Shader stage index where the array is used</param> /// <param name="stageIndex">Shader stage index where the array is used</param>
/// <param name="textureBufferIndex">Texture constant buffer index</param> /// <param name="textureBufferIndex">Texture constant buffer index</param>
/// <param name="bindingInfo">Array binding information</param> /// <param name="bindingInfo">Array binding information</param>
public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, TextureBindingInfo bindingInfo) public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, in TextureBindingInfo bindingInfo)
{ {
Update(texturePool, null, stage, stageIndex, textureBufferIndex, isImage: true, SamplerIndex.ViaHeaderIndex, bindingInfo); Update(texturePool, null, stage, stageIndex, textureBufferIndex, isImage: true, SamplerIndex.ViaHeaderIndex, bindingInfo);
} }
@ -603,7 +606,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int textureBufferIndex, int textureBufferIndex,
bool isImage, bool isImage,
SamplerIndex samplerIndex, SamplerIndex samplerIndex,
TextureBindingInfo bindingInfo) in TextureBindingInfo bindingInfo)
{ {
if (IsDirectHandleType(bindingInfo.Handle)) if (IsDirectHandleType(bindingInfo.Handle))
{ {
@ -623,7 +626,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="stage">Shader stage where the array is used</param> /// <param name="stage">Shader stage where the array is used</param>
/// <param name="isImage">Whether the array is a image or texture array</param> /// <param name="isImage">Whether the array is a image or texture array</param>
/// <param name="bindingInfo">Array binding information</param> /// <param name="bindingInfo">Array binding information</param>
private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, TextureBindingInfo bindingInfo) private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, in TextureBindingInfo bindingInfo)
{ {
CacheEntry entry = GetOrAddEntry(texturePool, samplerPool, bindingInfo, isImage, out bool isNewEntry); CacheEntry entry = GetOrAddEntry(texturePool, samplerPool, bindingInfo, isImage, out bool isNewEntry);
@ -638,11 +641,11 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage) if (isImage)
{ {
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); SetImageArray(stage, bindingInfo, entry.ImageArray);
} }
else else
{ {
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); SetTextureArray(stage, bindingInfo, entry.TextureArray);
} }
return; return;
@ -708,11 +711,11 @@ namespace Ryujinx.Graphics.Gpu.Image
format = texture.Format; format = texture.Format;
} }
_channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format); _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
} }
else else
{ {
_channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format); _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
} }
} }
else if (isImage) else if (isImage)
@ -737,14 +740,14 @@ namespace Ryujinx.Graphics.Gpu.Image
entry.ImageArray.SetFormats(0, formats); entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures); entry.ImageArray.SetImages(0, textures);
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); SetImageArray(stage, bindingInfo, entry.ImageArray);
} }
else else
{ {
entry.TextureArray.SetSamplers(0, samplers); entry.TextureArray.SetSamplers(0, samplers);
entry.TextureArray.SetTextures(0, textures); entry.TextureArray.SetTextures(0, textures);
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); SetTextureArray(stage, bindingInfo, entry.TextureArray);
} }
} }
@ -767,7 +770,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int textureBufferIndex, int textureBufferIndex,
bool isImage, bool isImage,
SamplerIndex samplerIndex, SamplerIndex samplerIndex,
TextureBindingInfo bindingInfo) in TextureBindingInfo bindingInfo)
{ {
(textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex); (textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex);
@ -800,11 +803,11 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage) if (isImage)
{ {
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); SetImageArray(stage, bindingInfo, entry.ImageArray);
} }
else else
{ {
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); SetTextureArray(stage, bindingInfo, entry.TextureArray);
} }
return; return;
@ -829,11 +832,11 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage) if (isImage)
{ {
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); SetImageArray(stage, bindingInfo, entry.ImageArray);
} }
else else
{ {
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); SetTextureArray(stage, bindingInfo, entry.TextureArray);
} }
return; return;
@ -899,13 +902,19 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
Sampler sampler = samplerPool?.Get(samplerId);
entry.TextureIds[textureId] = (texture, descriptor); entry.TextureIds[textureId] = (texture, descriptor);
entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture); ISampler hostSampler = null;
if (!isImage && bindingInfo.Target != Target.TextureBuffer)
{
Sampler sampler = samplerPool?.Get(samplerId);
entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
hostSampler = sampler?.GetHostSampler(texture);
}
Format format = bindingInfo.Format; Format format = bindingInfo.Format;
@ -921,11 +930,11 @@ namespace Ryujinx.Graphics.Gpu.Image
format = texture.Format; format = texture.Format;
} }
_channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format); _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
} }
else else
{ {
_channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format); _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
} }
} }
else if (isImage) else if (isImage)
@ -950,14 +959,50 @@ namespace Ryujinx.Graphics.Gpu.Image
entry.ImageArray.SetFormats(0, formats); entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures); entry.ImageArray.SetImages(0, textures);
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray); SetImageArray(stage, bindingInfo, entry.ImageArray);
} }
else else
{ {
entry.TextureArray.SetSamplers(0, samplers); entry.TextureArray.SetSamplers(0, samplers);
entry.TextureArray.SetTextures(0, textures); entry.TextureArray.SetTextures(0, textures);
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray); SetTextureArray(stage, bindingInfo, entry.TextureArray);
}
}
/// <summary>
/// Updates a texture array binding on the host.
/// </summary>
/// <param name="stage">Shader stage where the array is used</param>
/// <param name="bindingInfo">Array binding information</param>
/// <param name="array">Texture array</param>
private void SetTextureArray(ShaderStage stage, in TextureBindingInfo bindingInfo, ITextureArray array)
{
if (bindingInfo.Set >= _context.Capabilities.ExtraSetBaseIndex && _context.Capabilities.MaximumExtraSets != 0)
{
_context.Renderer.Pipeline.SetTextureArraySeparate(stage, bindingInfo.Set, array);
}
else
{
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, array);
}
}
/// <summary>
/// Updates a image array binding on the host.
/// </summary>
/// <param name="stage">Shader stage where the array is used</param>
/// <param name="bindingInfo">Array binding information</param>
/// <param name="array">Image array</param>
private void SetImageArray(ShaderStage stage, in TextureBindingInfo bindingInfo, IImageArray array)
{
if (bindingInfo.Set >= _context.Capabilities.ExtraSetBaseIndex && _context.Capabilities.MaximumExtraSets != 0)
{
_context.Renderer.Pipeline.SetImageArraySeparate(stage, bindingInfo.Set, array);
}
else
{
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, array);
} }
} }
@ -973,7 +1018,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private CacheEntry GetOrAddEntry( private CacheEntry GetOrAddEntry(
TexturePool texturePool, TexturePool texturePool,
SamplerPool samplerPool, SamplerPool samplerPool,
TextureBindingInfo bindingInfo, in TextureBindingInfo bindingInfo,
bool isImage, bool isImage,
out bool isNew) out bool isNew)
{ {
@ -1015,7 +1060,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private CacheEntryFromBuffer GetOrAddEntry( private CacheEntryFromBuffer GetOrAddEntry(
TexturePool texturePool, TexturePool texturePool,
SamplerPool samplerPool, SamplerPool samplerPool,
TextureBindingInfo bindingInfo, in TextureBindingInfo bindingInfo,
bool isImage, bool isImage,
ref BufferBounds textureBufferBounds, ref BufferBounds textureBufferBounds,
out bool isNew) out bool isNew)
@ -1077,6 +1122,15 @@ namespace Ryujinx.Graphics.Gpu.Image
nextNode = nextNode.Next; nextNode = nextNode.Next;
_cacheFromBuffer.Remove(toRemove.Value.Key); _cacheFromBuffer.Remove(toRemove.Value.Key);
_lruCache.Remove(toRemove); _lruCache.Remove(toRemove);
if (toRemove.Value.Key.IsImage)
{
toRemove.Value.ImageArray.Dispose();
}
else
{
toRemove.Value.TextureArray.Dispose();
}
} }
} }
@ -1088,11 +1142,20 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
List<CacheEntryFromPoolKey> keysToRemove = null; List<CacheEntryFromPoolKey> keysToRemove = null;
foreach (CacheEntryFromPoolKey key in _cacheFromPool.Keys) foreach ((CacheEntryFromPoolKey key, CacheEntry entry) in _cacheFromPool)
{ {
if (key.MatchesPool(pool)) if (key.MatchesPool(pool))
{ {
(keysToRemove ??= new()).Add(key); (keysToRemove ??= new()).Add(key);
if (key.IsImage)
{
entry.ImageArray.Dispose();
}
else
{
entry.TextureArray.Dispose();
}
} }
} }

View File

@ -468,13 +468,11 @@ namespace Ryujinx.Graphics.Gpu.Image
int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY(); int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ(); int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
layered &= size.UnpackIsLayered();
Target target; Target target;
if (dsState.MemoryLayout.UnpackIsTarget3D()) if ((samplesInX | samplesInY) != 1)
{
target = Target.Texture3D;
}
else if ((samplesInX | samplesInY) != 1)
{ {
target = size.Depth > 1 && layered target = size.Depth > 1 && layered
? Target.Texture2DMultisampleArray ? Target.Texture2DMultisampleArray

View File

@ -645,7 +645,7 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
else else
{ {
_flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.FlushPersistent); _flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.HostMemory);
_flushBufferImported = false; _flushBufferImported = false;
} }

View File

@ -6,6 +6,7 @@ using Ryujinx.Memory.Range;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics;
using System.Threading; using System.Threading;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
@ -490,6 +491,8 @@ namespace Ryujinx.Graphics.Gpu.Image
levels = (maxLod - minLod) + 1; levels = (maxLod - minLod) + 1;
} }
levels = ClampLevels(target, width, height, depthOrLayers, levels);
SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert(); SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert();
SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert(); SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert();
SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert(); SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert();
@ -540,6 +543,34 @@ namespace Ryujinx.Graphics.Gpu.Image
swizzleA); swizzleA);
} }
/// <summary>
/// Clamps the amount of mipmap levels to the maximum allowed for the given texture dimensions.
/// </summary>
/// <param name="target">Number of texture dimensions (1D, 2D, 3D, Cube, etc)</param>
/// <param name="width">Width of the texture</param>
/// <param name="height">Height of the texture, ignored for 1D textures</param>
/// <param name="depthOrLayers">Depth of the texture for 3D textures, otherwise ignored</param>
/// <param name="levels">Original amount of mipmap levels</param>
/// <returns>Clamped mipmap levels</returns>
private static int ClampLevels(Target target, int width, int height, int depthOrLayers, int levels)
{
int maxSize = width;
if (target != Target.Texture1D &&
target != Target.Texture1DArray)
{
maxSize = Math.Max(maxSize, height);
}
if (target == Target.Texture3D)
{
maxSize = Math.Max(maxSize, depthOrLayers);
}
int maxLevels = BitOperations.Log2((uint)maxSize) + 1;
return Math.Min(levels, maxLevels);
}
/// <summary> /// <summary>
/// Gets the texture depth-stencil mode, based on the swizzle components of each color channel. /// Gets the texture depth-stencil mode, based on the swizzle components of each color channel.
/// The depth-stencil mode is determined based on how the driver sets those parameters. /// The depth-stencil mode is determined based on how the driver sets those parameters.

View File

@ -10,6 +10,8 @@ using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory namespace Ryujinx.Graphics.Gpu.Memory
{ {
delegate void BufferFlushAction(ulong address, ulong size, ulong syncNumber);
/// <summary> /// <summary>
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others. /// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
/// </summary> /// </summary>
@ -23,7 +25,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Host buffer handle. /// Host buffer handle.
/// </summary> /// </summary>
public BufferHandle Handle { get; } public BufferHandle Handle { get; private set; }
/// <summary> /// <summary>
/// Start address of the buffer in guest memory. /// Start address of the buffer in guest memory.
@ -60,6 +62,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </remarks> /// </remarks>
private BufferModifiedRangeList _modifiedRanges = null; private BufferModifiedRangeList _modifiedRanges = null;
/// <summary>
/// A structure that is used to flush buffer data back to a host mapped buffer for cached readback.
/// Only used if the buffer data is explicitly owned by device local memory.
/// </summary>
private BufferPreFlush _preFlush = null;
/// <summary>
/// Usage tracking state that determines what type of backing the buffer should use.
/// </summary>
public BufferBackingState BackingState;
private readonly MultiRegionHandle _memoryTrackingGranular; private readonly MultiRegionHandle _memoryTrackingGranular;
private readonly RegionHandle _memoryTracking; private readonly RegionHandle _memoryTracking;
@ -87,6 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="physicalMemory">Physical memory where the buffer is mapped</param> /// <param name="physicalMemory">Physical memory where the buffer is mapped</param>
/// <param name="address">Start address of the buffer</param> /// <param name="address">Start address of the buffer</param>
/// <param name="size">Size of the buffer in bytes</param> /// <param name="size">Size of the buffer in bytes</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param> /// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
/// <param name="baseBuffers">Buffers which this buffer contains, and will inherit tracking handles from</param> /// <param name="baseBuffers">Buffers which this buffer contains, and will inherit tracking handles from</param>
public Buffer( public Buffer(
@ -94,6 +108,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
PhysicalMemory physicalMemory, PhysicalMemory physicalMemory,
ulong address, ulong address,
ulong size, ulong size,
BufferStage stage,
bool sparseCompatible, bool sparseCompatible,
IEnumerable<Buffer> baseBuffers = null) IEnumerable<Buffer> baseBuffers = null)
{ {
@ -103,9 +118,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
Size = size; Size = size;
SparseCompatible = sparseCompatible; SparseCompatible = sparseCompatible;
BufferAccess access = sparseCompatible ? BufferAccess.SparseCompatible : BufferAccess.Default; BackingState = new BufferBackingState(_context, this, stage, baseBuffers);
Handle = context.Renderer.CreateBuffer((int)size, access, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null); BufferAccess access = BackingState.SwitchAccess(this);
Handle = context.Renderer.CreateBuffer((int)size, access);
_useGranular = size > GranularBufferThreshold; _useGranular = size > GranularBufferThreshold;
@ -161,6 +178,29 @@ namespace Ryujinx.Graphics.Gpu.Memory
_virtualDependenciesLock = new ReaderWriterLockSlim(); _virtualDependenciesLock = new ReaderWriterLockSlim();
} }
/// <summary>
/// Recreates the backing buffer based on the desired access type
/// reported by the backing state struct.
/// </summary>
private void ChangeBacking()
{
BufferAccess access = BackingState.SwitchAccess(this);
BufferHandle newHandle = _context.Renderer.CreateBuffer((int)Size, access);
_context.Renderer.Pipeline.CopyBuffer(Handle, newHandle, 0, 0, (int)Size);
_modifiedRanges?.SelfMigration();
// If swtiching from device local to host mapped, pre-flushing data no longer makes sense.
// This is set to null and disposed when the migration fully completes.
_preFlush = null;
Handle = newHandle;
_physicalMemory.BufferCache.BufferBackingChanged(this);
}
/// <summary> /// <summary>
/// Gets a sub-range from the buffer, from a start address til a page boundary after the given size. /// Gets a sub-range from the buffer, from a start address til a page boundary after the given size.
/// </summary> /// </summary>
@ -246,6 +286,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
BackingState.RecordSet();
_context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size)); _context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
CopyToDependantVirtualBuffers(); CopyToDependantVirtualBuffers();
} }
@ -283,15 +324,35 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges ??= new BufferModifiedRangeList(_context, this, Flush); _modifiedRanges ??= new BufferModifiedRangeList(_context, this, Flush);
} }
/// <summary>
/// Checks if a backing change is deemed necessary from the given usage.
/// If it is, queues a backing change to happen on the next sync action.
/// </summary>
/// <param name="stage">Buffer stage that can change backing type</param>
private void TryQueueBackingChange(BufferStage stage)
{
if (BackingState.ShouldChangeBacking(stage))
{
if (!_syncActionRegistered)
{
_context.RegisterSyncAction(this);
_syncActionRegistered = true;
}
}
}
/// <summary> /// <summary>
/// Signal that the given region of the buffer has been modified. /// Signal that the given region of the buffer has been modified.
/// </summary> /// </summary>
/// <param name="address">The start address of the modified region</param> /// <param name="address">The start address of the modified region</param>
/// <param name="size">The size of the modified region</param> /// <param name="size">The size of the modified region</param>
public void SignalModified(ulong address, ulong size) /// <param name="stage">Buffer stage that triggered the modification</param>
public void SignalModified(ulong address, ulong size, BufferStage stage)
{ {
EnsureRangeList(); EnsureRangeList();
TryQueueBackingChange(stage);
_modifiedRanges.SignalModified(address, size); _modifiedRanges.SignalModified(address, size);
if (!_syncActionRegistered) if (!_syncActionRegistered)
@ -311,6 +372,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges?.Clear(address, size); _modifiedRanges?.Clear(address, size);
} }
/// <summary>
/// Action to be performed immediately before sync is created.
/// This will copy any buffer ranges designated for pre-flushing.
/// </summary>
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
public void SyncPreAction(bool syncpoint)
{
if (_referenceCount == 0)
{
return;
}
if (BackingState.ShouldChangeBacking())
{
ChangeBacking();
}
if (BackingState.IsDeviceLocal)
{
_preFlush ??= new BufferPreFlush(_context, this, FlushImpl);
if (_preFlush.ShouldCopy)
{
_modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, (address, size) =>
{
_preFlush.CopyModified(address, size);
});
}
}
}
/// <summary> /// <summary>
/// Action to be performed when a syncpoint is reached after modification. /// Action to be performed when a syncpoint is reached after modification.
/// This will register read/write tracking to flush the buffer from GPU when its memory is used. /// This will register read/write tracking to flush the buffer from GPU when its memory is used.
@ -466,6 +558,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="mSize">Size of the modified region</param> /// <param name="mSize">Size of the modified region</param>
private void LoadRegion(ulong mAddress, ulong mSize) private void LoadRegion(ulong mAddress, ulong mSize)
{ {
BackingState.RecordSet();
int offset = (int)(mAddress - Address); int offset = (int)(mAddress - Address);
_context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize)); _context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
@ -539,18 +633,84 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Flushes a range of the buffer. /// Flushes a range of the buffer.
/// This writes the range data back into guest memory. /// This writes the range data back into guest memory.
/// </summary> /// </summary>
/// <param name="handle">Buffer handle to flush data from</param>
/// <param name="address">Start address of the range</param> /// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param> /// <param name="size">Size in bytes of the range</param>
public void Flush(ulong address, ulong size) private void FlushImpl(BufferHandle handle, ulong address, ulong size)
{ {
int offset = (int)(address - Address); int offset = (int)(address - Address);
using PinnedSpan<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. // TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
_physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size)); _physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size));
} }
/// <summary>
/// Flushes a range of the buffer.
/// This writes the range data back into guest memory.
/// </summary>
/// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param>
private void FlushImpl(ulong address, ulong size)
{
FlushImpl(Handle, address, size);
}
/// <summary>
/// Flushes a range of the buffer from the most optimal source.
/// This writes the range data back into guest memory.
/// </summary>
/// <param name="address">Start address of the range</param>
/// <param name="size">Size in bytes of the range</param>
/// <param name="syncNumber">Sync number waited for before flushing the data</param>
public void Flush(ulong address, ulong size, ulong syncNumber)
{
BackingState.RecordFlush();
BufferPreFlush preFlush = _preFlush;
if (preFlush != null)
{
preFlush.FlushWithAction(address, size, syncNumber);
}
else
{
FlushImpl(address, size);
}
}
/// <summary>
/// Gets an action that disposes the backing buffer using its current handle.
/// Useful for deleting an old copy of the buffer after the handle changes.
/// </summary>
/// <returns>An action that flushes data from the specified range, using the buffer handle at the time the method is generated</returns>
public Action GetSnapshotDisposeAction()
{
BufferHandle handle = Handle;
BufferPreFlush preFlush = _preFlush;
return () =>
{
_context.Renderer.DeleteBuffer(handle);
preFlush?.Dispose();
};
}
/// <summary>
/// Gets an action that flushes a range of the buffer using its current handle.
/// Useful for flushing data from old copies of the buffer after the handle changes.
/// </summary>
/// <returns>An action that flushes data from the specified range, using the buffer handle at the time the method is generated</returns>
public BufferFlushAction GetSnapshotFlushAction()
{
BufferHandle handle = Handle;
return (ulong address, ulong size, ulong _) =>
{
FlushImpl(handle, address, size);
};
}
/// <summary> /// <summary>
/// Align a given address and size region to page boundaries. /// Align a given address and size region to page boundaries.
/// </summary> /// </summary>
@ -857,6 +1017,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges?.Clear(); _modifiedRanges?.Clear();
_context.Renderer.DeleteBuffer(Handle); _context.Renderer.DeleteBuffer(Handle);
_preFlush?.Dispose();
_preFlush = null;
UnmappedSequence++; UnmappedSequence++;
} }

View File

@ -0,0 +1,294 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
/// Type of backing memory.
/// In ascending order of priority when merging multiple buffer backing states.
/// </summary>
internal enum BufferBackingType
{
HostMemory,
DeviceMemory,
DeviceMemoryWithFlush
}
/// <summary>
/// Keeps track of buffer usage to decide what memory heap that buffer memory is placed on.
/// Dedicated GPUs prefer certain types of resources to be device local,
/// and if we need data to be read back, we might prefer that they're in host memory.
///
/// The measurements recorded here compare to a set of heruristics (thresholds and conditions)
/// that appear to produce good performance in most software.
/// </summary>
internal struct BufferBackingState
{
private const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb
private const int SetCountThreshold = 100;
private const int WriteCountThreshold = 50;
private const int FlushCountThreshold = 5;
private const int DeviceLocalForceExpiry = 100;
public readonly bool IsDeviceLocal => _activeType != BufferBackingType.HostMemory;
private readonly SystemMemoryType _systemMemoryType;
private BufferBackingType _activeType;
private BufferBackingType _desiredType;
private bool _canSwap;
private int _setCount;
private int _writeCount;
private int _flushCount;
private int _flushTemp;
private int _lastFlushWrite;
private int _deviceLocalForceCount;
private readonly int _size;
/// <summary>
/// Initialize the buffer backing state for a given parent buffer.
/// </summary>
/// <param name="context">GPU context</param>
/// <param name="parent">Parent buffer</param>
/// <param name="stage">Initial buffer stage</param>
/// <param name="baseBuffers">Buffers to inherit state from</param>
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, IEnumerable<Buffer> baseBuffers = null)
{
_size = (int)parent.Size;
_systemMemoryType = context.Capabilities.MemoryType;
// Backend managed is always auto, unified memory is always host.
_desiredType = BufferBackingType.HostMemory;
_canSwap = _systemMemoryType != SystemMemoryType.BackendManaged && _systemMemoryType != SystemMemoryType.UnifiedMemory;
if (_canSwap)
{
// Might want to start certain buffers as being device local,
// and the usage might also lock those buffers into being device local.
BufferStage storageFlags = stage & BufferStage.StorageMask;
if (parent.Size > DeviceLocalSizeThreshold && baseBuffers == null)
{
_desiredType = BufferBackingType.DeviceMemory;
}
if (storageFlags != 0)
{
// Storage buffer bindings may require special treatment.
var rawStage = stage & BufferStage.StageMask;
if (rawStage == BufferStage.Fragment)
{
// Fragment read should start device local.
_desiredType = BufferBackingType.DeviceMemory;
if (storageFlags != BufferStage.StorageRead)
{
// Fragment write should stay device local until the use doesn't happen anymore.
_deviceLocalForceCount = DeviceLocalForceExpiry;
}
}
// TODO: Might be nice to force atomic access to be device local for any stage.
}
if (baseBuffers != null)
{
foreach (Buffer buffer in baseBuffers)
{
CombineState(buffer.BackingState);
}
}
}
}
/// <summary>
/// Combine buffer backing types, selecting the one with highest priority.
/// </summary>
/// <param name="left">First buffer backing type</param>
/// <param name="right">Second buffer backing type</param>
/// <returns>Combined buffer backing type</returns>
private static BufferBackingType CombineTypes(BufferBackingType left, BufferBackingType right)
{
return (BufferBackingType)Math.Max((int)left, (int)right);
}
/// <summary>
/// Combine the state from the given buffer backing state with this one,
/// so that the state isn't lost when migrating buffers.
/// </summary>
/// <param name="oldState">Buffer state to combine into this state</param>
private void CombineState(BufferBackingState oldState)
{
_setCount += oldState._setCount;
_writeCount += oldState._writeCount;
_flushCount += oldState._flushCount;
_flushTemp += oldState._flushTemp;
_lastFlushWrite = -1;
_deviceLocalForceCount = Math.Max(_deviceLocalForceCount, oldState._deviceLocalForceCount);
_canSwap &= oldState._canSwap;
_desiredType = CombineTypes(_desiredType, oldState._desiredType);
}
/// <summary>
/// Get the buffer access for the desired backing type, and record that type as now being active.
/// </summary>
/// <param name="parent">Parent buffer</param>
/// <returns>Buffer access</returns>
public BufferAccess SwitchAccess(Buffer parent)
{
BufferAccess access = parent.SparseCompatible ? BufferAccess.SparseCompatible : BufferAccess.Default;
bool isBackendManaged = _systemMemoryType == SystemMemoryType.BackendManaged;
if (!isBackendManaged)
{
switch (_desiredType)
{
case BufferBackingType.HostMemory:
access |= BufferAccess.HostMemory;
break;
case BufferBackingType.DeviceMemory:
access |= BufferAccess.DeviceMemory;
break;
case BufferBackingType.DeviceMemoryWithFlush:
access |= BufferAccess.DeviceMemoryMapped;
break;
}
}
_activeType = _desiredType;
return access;
}
/// <summary>
/// Record when data has been uploaded to the buffer.
/// </summary>
public void RecordSet()
{
_setCount++;
ConsiderUseCounts();
}
/// <summary>
/// Record when data has been flushed from the buffer.
/// </summary>
public void RecordFlush()
{
if (_lastFlushWrite != _writeCount)
{
// If it's on the same page as the last flush, ignore it.
_lastFlushWrite = _writeCount;
_flushCount++;
}
}
/// <summary>
/// Determine if the buffer backing should be changed.
/// </summary>
/// <returns>True if the desired backing type is different from the current type</returns>
public readonly bool ShouldChangeBacking()
{
return _desiredType != _activeType;
}
/// <summary>
/// Determine if the buffer backing should be changed, considering a new use with the given buffer stage.
/// </summary>
/// <param name="stage">Buffer stage for the use</param>
/// <returns>True if the desired backing type is different from the current type</returns>
public bool ShouldChangeBacking(BufferStage stage)
{
if (!_canSwap)
{
return false;
}
BufferStage storageFlags = stage & BufferStage.StorageMask;
if (storageFlags != 0)
{
if (storageFlags != BufferStage.StorageRead)
{
// Storage write.
_writeCount++;
var rawStage = stage & BufferStage.StageMask;
if (rawStage == BufferStage.Fragment)
{
// Switch to device memory, swap back only if this use disappears.
_desiredType = CombineTypes(_desiredType, BufferBackingType.DeviceMemory);
_deviceLocalForceCount = DeviceLocalForceExpiry;
// TODO: Might be nice to force atomic access to be device local for any stage.
}
}
ConsiderUseCounts();
}
return _desiredType != _activeType;
}
/// <summary>
/// Evaluate the current counts to determine what the buffer's desired backing type is.
/// This method depends on heuristics devised by testing a variety of software.
/// </summary>
private void ConsiderUseCounts()
{
if (_canSwap)
{
if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
{
if (_deviceLocalForceCount > 0 && --_deviceLocalForceCount != 0)
{
// Some buffer usage demanded that the buffer stay device local.
// The desired type was selected when this counter was set.
}
else if (_flushCount > 0 || _flushTemp-- > 0)
{
// Buffers that flush should ideally be mapped in host address space for easy copies.
// If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
// If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
_desiredType = _size > DeviceLocalSizeThreshold ? BufferBackingType.DeviceMemoryWithFlush : BufferBackingType.HostMemory;
}
else if (_writeCount >= WriteCountThreshold)
{
// Buffers that are written often should ideally be in the device local heap. (Storage buffers)
_desiredType = BufferBackingType.DeviceMemory;
}
else if (_setCount > SetCountThreshold)
{
// Buffers that have their data set often should ideally be host mapped. (Constant buffers)
_desiredType = BufferBackingType.HostMemory;
}
// It's harder for a buffer that is flushed to revert to another type of mapping.
if (_flushCount > 0)
{
_flushTemp = 1000;
}
_lastFlushWrite = -1;
_flushCount = 0;
_writeCount = 0;
_setCount = 0;
}
}
}
}
}

View File

@ -107,8 +107,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param> /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
/// <param name="gpuVa">Start GPU virtual address of the buffer</param> /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <returns>Contiguous physical range of the buffer, after address translation</returns> /// <returns>Contiguous physical range of the buffer, after address translation</returns>
public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size) public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
{ {
if (gpuVa == 0) if (gpuVa == 0)
{ {
@ -119,7 +120,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (address != MemoryManager.PteUnmapped) if (address != MemoryManager.PteUnmapped)
{ {
CreateBuffer(address, size); CreateBuffer(address, size, stage);
} }
return new MultiRange(address, size); return new MultiRange(address, size);
@ -132,8 +133,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param> /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
/// <param name="gpuVa">Start GPU virtual address of the buffer</param> /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <returns>Physical ranges of the buffer, after address translation</returns> /// <returns>Physical ranges of the buffer, after address translation</returns>
public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size) public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
{ {
if (gpuVa == 0) if (gpuVa == 0)
{ {
@ -149,7 +151,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
return range; return range;
} }
CreateBuffer(range); CreateBuffer(range, stage);
return range; return range;
} }
@ -161,8 +163,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param> /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
/// <param name="gpuVa">Start GPU virtual address of the buffer</param> /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <returns>Physical ranges of the buffer, after address translation</returns> /// <returns>Physical ranges of the buffer, after address translation</returns>
public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size) public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
{ {
if (gpuVa == 0) if (gpuVa == 0)
{ {
@ -186,11 +189,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
if (range.Count > 1) if (range.Count > 1)
{ {
CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize); CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize);
} }
else else
{ {
CreateBuffer(subRange.Address, subRange.Size); CreateBuffer(subRange.Address, subRange.Size, stage);
} }
} }
} }
@ -203,11 +206,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// This can be used to ensure the existance of a buffer. /// This can be used to ensure the existance of a buffer.
/// </summary> /// </summary>
/// <param name="range">Physical ranges of memory where the buffer data is located</param> /// <param name="range">Physical ranges of memory where the buffer data is located</param>
public void CreateBuffer(MultiRange range) /// <param name="stage">The type of usage that created the buffer</param>
public void CreateBuffer(MultiRange range, BufferStage stage)
{ {
if (range.Count > 1) if (range.Count > 1)
{ {
CreateMultiRangeBuffer(range); CreateMultiRangeBuffer(range, stage);
} }
else else
{ {
@ -215,7 +219,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped) if (subRange.Address != MemoryManager.PteUnmapped)
{ {
CreateBuffer(subRange.Address, subRange.Size); CreateBuffer(subRange.Address, subRange.Size, stage);
} }
} }
} }
@ -226,7 +230,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Address of the buffer in memory</param> /// <param name="address">Address of the buffer in memory</param>
/// <param name="size">Size of the buffer in bytes</param> /// <param name="size">Size of the buffer in bytes</param>
public void CreateBuffer(ulong address, ulong size) /// <param name="stage">The type of usage that created the buffer</param>
public void CreateBuffer(ulong address, ulong size, BufferStage stage)
{ {
ulong endAddress = address + size; ulong endAddress = address + size;
@ -239,7 +244,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
alignedEndAddress += BufferAlignmentSize; alignedEndAddress += BufferAlignmentSize;
} }
CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress); CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage);
} }
/// <summary> /// <summary>
@ -248,8 +253,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Address of the buffer in memory</param> /// <param name="address">Address of the buffer in memory</param>
/// <param name="size">Size of the buffer in bytes</param> /// <param name="size">Size of the buffer in bytes</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <param name="alignment">Alignment of the start address of the buffer in bytes</param> /// <param name="alignment">Alignment of the start address of the buffer in bytes</param>
public void CreateBuffer(ulong address, ulong size, ulong alignment) public void CreateBuffer(ulong address, ulong size, BufferStage stage, ulong alignment)
{ {
ulong alignmentMask = alignment - 1; ulong alignmentMask = alignment - 1;
ulong pageAlignmentMask = BufferAlignmentMask; ulong pageAlignmentMask = BufferAlignmentMask;
@ -264,7 +270,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
alignedEndAddress += pageAlignmentMask; alignedEndAddress += pageAlignmentMask;
} }
CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, alignment); CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage, alignment);
} }
/// <summary> /// <summary>
@ -272,7 +278,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// if it does not exist yet. /// if it does not exist yet.
/// </summary> /// </summary>
/// <param name="range">Physical ranges of memory</param> /// <param name="range">Physical ranges of memory</param>
private void CreateMultiRangeBuffer(MultiRange range) /// <param name="stage">The type of usage that created the buffer</param>
private void CreateMultiRangeBuffer(MultiRange range, BufferStage stage)
{ {
// Ensure all non-contiguous buffer we might use are sparse aligned. // Ensure all non-contiguous buffer we might use are sparse aligned.
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
@ -281,7 +288,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped) if (subRange.Address != MemoryManager.PteUnmapped)
{ {
CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize); CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize);
} }
} }
@ -431,9 +438,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
result.EndGpuAddress < gpuVa + size || result.EndGpuAddress < gpuVa + size ||
result.UnmappedSequence != result.Buffer.UnmappedSequence) result.UnmappedSequence != result.Buffer.UnmappedSequence)
{ {
MultiRange range = TranslateAndCreateBuffer(memoryManager, gpuVa, size); MultiRange range = TranslateAndCreateBuffer(memoryManager, gpuVa, size, BufferStage.Internal);
ulong address = range.GetSubRange(0).Address; ulong address = range.GetSubRange(0).Address;
result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size)); result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size, BufferStage.Internal));
_dirtyCache[gpuVa] = result; _dirtyCache[gpuVa] = result;
} }
@ -466,9 +473,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
result.EndGpuAddress < alignedEndGpuVa || result.EndGpuAddress < alignedEndGpuVa ||
result.UnmappedSequence != result.Buffer.UnmappedSequence) result.UnmappedSequence != result.Buffer.UnmappedSequence)
{ {
MultiRange range = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size); MultiRange range = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size, BufferStage.None);
ulong address = range.GetSubRange(0).Address; ulong address = range.GetSubRange(0).Address;
result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size)); result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size, BufferStage.None));
_modifiedCache[alignedGpuVa] = result; _modifiedCache[alignedGpuVa] = result;
} }
@ -485,7 +492,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Address of the buffer in guest memory</param> /// <param name="address">Address of the buffer in guest memory</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
private void CreateBufferAligned(ulong address, ulong size) /// <param name="stage">The type of usage that created the buffer</param>
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
{ {
Buffer[] overlaps = _bufferOverlaps; Buffer[] overlaps = _bufferOverlaps;
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps); int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
@ -546,13 +554,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address; ulong newSize = endAddress - address;
CreateBufferAligned(address, newSize, anySparseCompatible, overlaps, overlapsCount); CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps, overlapsCount);
} }
} }
else else
{ {
// No overlap, just create a new buffer. // No overlap, just create a new buffer.
Buffer buffer = new(_context, _physicalMemory, address, size, sparseCompatible: false); Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false);
lock (_buffers) lock (_buffers)
{ {
@ -570,8 +578,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Address of the buffer in guest memory</param> /// <param name="address">Address of the buffer in guest memory</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <param name="alignment">Alignment of the start address of the buffer</param> /// <param name="alignment">Alignment of the start address of the buffer</param>
private void CreateBufferAligned(ulong address, ulong size, ulong alignment) private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
{ {
Buffer[] overlaps = _bufferOverlaps; Buffer[] overlaps = _bufferOverlaps;
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps); int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
@ -624,13 +633,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address; ulong newSize = endAddress - address;
CreateBufferAligned(address, newSize, sparseAligned, overlaps, overlapsCount); CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps, overlapsCount);
} }
} }
else else
{ {
// No overlap, just create a new buffer. // No overlap, just create a new buffer.
Buffer buffer = new(_context, _physicalMemory, address, size, sparseAligned); Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned);
lock (_buffers) lock (_buffers)
{ {
@ -648,12 +657,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Address of the buffer in guest memory</param> /// <param name="address">Address of the buffer in guest memory</param>
/// <param name="size">Size in bytes of the buffer</param> /// <param name="size">Size in bytes of the buffer</param>
/// <param name="stage">The type of usage that created the buffer</param>
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param> /// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
/// <param name="overlaps">Buffers overlapping the range</param> /// <param name="overlaps">Buffers overlapping the range</param>
/// <param name="overlapsCount">Total of overlaps</param> /// <param name="overlapsCount">Total of overlaps</param>
private void CreateBufferAligned(ulong address, ulong size, bool sparseCompatible, Buffer[] overlaps, int overlapsCount) private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps, int overlapsCount)
{ {
Buffer newBuffer = new Buffer(_context, _physicalMemory, address, size, sparseCompatible, overlaps.Take(overlapsCount)); Buffer newBuffer = new Buffer(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps.Take(overlapsCount));
lock (_buffers) lock (_buffers)
{ {
@ -704,7 +714,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int index = 0; index < overlapCount; index++) for (int index = 0; index < overlapCount; index++)
{ {
CreateMultiRangeBuffer(overlaps[index].Range); CreateMultiRangeBuffer(overlaps[index].Range, BufferStage.None);
} }
} }
@ -731,8 +741,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the copy</param> /// <param name="size">Size in bytes of the copy</param>
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size) public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
{ {
MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size); MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size, BufferStage.Copy);
MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size); MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size, BufferStage.Copy);
if (srcRange.Count == 1 && dstRange.Count == 1) if (srcRange.Count == 1 && dstRange.Count == 1)
{ {
@ -788,8 +798,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the copy</param> /// <param name="size">Size in bytes of the copy</param>
private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size) private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size)
{ {
Buffer srcBuffer = GetBuffer(srcAddress, size); Buffer srcBuffer = GetBuffer(srcAddress, size, BufferStage.Copy);
Buffer dstBuffer = GetBuffer(dstAddress, size); Buffer dstBuffer = GetBuffer(dstAddress, size, BufferStage.Copy);
int srcOffset = (int)(srcAddress - srcBuffer.Address); int srcOffset = (int)(srcAddress - srcBuffer.Address);
int dstOffset = (int)(dstAddress - dstBuffer.Address); int dstOffset = (int)(dstAddress - dstBuffer.Address);
@ -803,7 +813,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (srcBuffer.IsModified(srcAddress, size)) if (srcBuffer.IsModified(srcAddress, size))
{ {
dstBuffer.SignalModified(dstAddress, size); dstBuffer.SignalModified(dstAddress, size, BufferStage.Copy);
} }
else else
{ {
@ -828,12 +838,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="value">Value to be written into the buffer</param> /// <param name="value">Value to be written into the buffer</param>
public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value) public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value)
{ {
MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size); MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size, BufferStage.Copy);
for (int index = 0; index < range.Count; index++) for (int index = 0; index < range.Count; index++)
{ {
MemoryRange subRange = range.GetSubRange(index); MemoryRange subRange = range.GetSubRange(index);
Buffer buffer = GetBuffer(subRange.Address, subRange.Size); Buffer buffer = GetBuffer(subRange.Address, subRange.Size, BufferStage.Copy);
int offset = (int)(subRange.Address - buffer.Address); int offset = (int)(subRange.Address - buffer.Address);
@ -849,18 +859,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Gets a buffer sub-range starting at a given memory address, aligned to the next page boundary. /// Gets a buffer sub-range starting at a given memory address, aligned to the next page boundary.
/// </summary> /// </summary>
/// <param name="range">Physical regions of memory where the buffer is mapped</param> /// <param name="range">Physical regions of memory where the buffer is mapped</param>
/// <param name="stage">Buffer stage that triggered the access</param>
/// <param name="write">Whether the buffer will be written to by this use</param> /// <param name="write">Whether the buffer will be written to by this use</param>
/// <returns>The buffer sub-range starting at the given memory address</returns> /// <returns>The buffer sub-range starting at the given memory address</returns>
public BufferRange GetBufferRangeAligned(MultiRange range, bool write = false) public BufferRange GetBufferRangeAligned(MultiRange range, BufferStage stage, bool write = false)
{ {
if (range.Count > 1) if (range.Count > 1)
{ {
return GetBuffer(range, write).GetRange(range); return GetBuffer(range, stage, write).GetRange(range);
} }
else else
{ {
MemoryRange subRange = range.GetSubRange(0); MemoryRange subRange = range.GetSubRange(0);
return GetBuffer(subRange.Address, subRange.Size, write).GetRangeAligned(subRange.Address, subRange.Size, write); return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRangeAligned(subRange.Address, subRange.Size, write);
} }
} }
@ -868,18 +879,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Gets a buffer sub-range for a given memory range. /// Gets a buffer sub-range for a given memory range.
/// </summary> /// </summary>
/// <param name="range">Physical regions of memory where the buffer is mapped</param> /// <param name="range">Physical regions of memory where the buffer is mapped</param>
/// <param name="stage">Buffer stage that triggered the access</param>
/// <param name="write">Whether the buffer will be written to by this use</param> /// <param name="write">Whether the buffer will be written to by this use</param>
/// <returns>The buffer sub-range for the given range</returns> /// <returns>The buffer sub-range for the given range</returns>
public BufferRange GetBufferRange(MultiRange range, bool write = false) public BufferRange GetBufferRange(MultiRange range, BufferStage stage, bool write = false)
{ {
if (range.Count > 1) if (range.Count > 1)
{ {
return GetBuffer(range, write).GetRange(range); return GetBuffer(range, stage, write).GetRange(range);
} }
else else
{ {
MemoryRange subRange = range.GetSubRange(0); MemoryRange subRange = range.GetSubRange(0);
return GetBuffer(subRange.Address, subRange.Size, write).GetRange(subRange.Address, subRange.Size, write); return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRange(subRange.Address, subRange.Size, write);
} }
} }
@ -888,9 +900,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// A buffer overlapping with the specified range is assumed to already exist on the cache. /// A buffer overlapping with the specified range is assumed to already exist on the cache.
/// </summary> /// </summary>
/// <param name="range">Physical regions of memory where the buffer is mapped</param> /// <param name="range">Physical regions of memory where the buffer is mapped</param>
/// <param name="stage">Buffer stage that triggered the access</param>
/// <param name="write">Whether the buffer will be written to by this use</param> /// <param name="write">Whether the buffer will be written to by this use</param>
/// <returns>The buffer where the range is fully contained</returns> /// <returns>The buffer where the range is fully contained</returns>
private MultiRangeBuffer GetBuffer(MultiRange range, bool write = false) private MultiRangeBuffer GetBuffer(MultiRange range, BufferStage stage, bool write = false)
{ {
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
{ {
@ -902,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (write) if (write)
{ {
subBuffer.SignalModified(subRange.Address, subRange.Size); subBuffer.SignalModified(subRange.Address, subRange.Size, stage);
} }
} }
@ -935,9 +948,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
/// <param name="address">Start address of the memory range</param> /// <param name="address">Start address of the memory range</param>
/// <param name="size">Size in bytes of the memory range</param> /// <param name="size">Size in bytes of the memory range</param>
/// <param name="stage">Buffer stage that triggered the access</param>
/// <param name="write">Whether the buffer will be written to by this use</param> /// <param name="write">Whether the buffer will be written to by this use</param>
/// <returns>The buffer where the range is fully contained</returns> /// <returns>The buffer where the range is fully contained</returns>
private Buffer GetBuffer(ulong address, ulong size, bool write = false) private Buffer GetBuffer(ulong address, ulong size, BufferStage stage, bool write = false)
{ {
Buffer buffer; Buffer buffer;
@ -950,7 +964,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (write) if (write)
{ {
buffer.SignalModified(address, size); buffer.SignalModified(address, size, stage);
} }
} }
else else
@ -1004,6 +1018,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
} }
/// <summary>
/// Signal that the given buffer's handle has changed,
/// forcing rebind and any overlapping multi-range buffers to be recreated.
/// </summary>
/// <param name="buffer">The buffer that has changed handle</param>
public void BufferBackingChanged(Buffer buffer)
{
NotifyBuffersModified?.Invoke();
RecreateMultiRangeBuffers(buffer.Address, buffer.Size);
}
/// <summary> /// <summary>
/// Prune any invalid entries from a quick access dictionary. /// Prune any invalid entries from a quick access dictionary.
/// </summary> /// </summary>

View File

@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="type">Type of each index buffer element</param> /// <param name="type">Type of each index buffer element</param>
public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type) public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
{ {
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer);
_indexBuffer.Range = range; _indexBuffer.Range = range;
_indexBuffer.Type = type; _indexBuffer.Type = type;
@ -186,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="divisor">Vertex divisor of the buffer, for instanced draws</param> /// <param name="divisor">Vertex divisor of the buffer, for instanced draws</param>
public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor) public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
{ {
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer);
_vertexBuffers[index].Range = range; _vertexBuffers[index].Range = range;
_vertexBuffers[index].Stride = stride; _vertexBuffers[index].Stride = stride;
@ -213,7 +213,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the transform feedback buffer</param> /// <param name="size">Size in bytes of the transform feedback buffer</param>
public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size) public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size)
{ {
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback);
_transformFeedbackBuffers[index] = new BufferBounds(range); _transformFeedbackBuffers[index] = new BufferBounds(range);
_transformFeedbackBuffersDirty = true; _transformFeedbackBuffersDirty = true;
@ -260,7 +260,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment); gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
_cpStorageBuffers.SetBounds(index, range, flags); _cpStorageBuffers.SetBounds(index, range, flags);
} }
@ -284,7 +284,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment); gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
if (!buffers.Buffers[index].Range.Equals(range)) if (!buffers.Buffers[index].Range.Equals(range))
{ {
@ -303,7 +303,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the storage buffer</param> /// <param name="size">Size in bytes of the storage buffer</param>
public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size) public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
{ {
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute);
_cpUniformBuffers.SetBounds(index, range); _cpUniformBuffers.SetBounds(index, range);
} }
@ -318,7 +318,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the storage buffer</param> /// <param name="size">Size in bytes of the storage buffer</param>
public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size) public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
{ {
MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage));
_gpUniformBuffers[stage].SetBounds(index, range); _gpUniformBuffers[stage].SetBounds(index, range);
_gpUniformBuffersDirty = true; _gpUniformBuffersDirty = true;
@ -502,7 +502,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferTextures) foreach (var binding in _bufferTextures)
{ {
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
var range = bufferCache.GetBufferRange(binding.Range, isStore); var range = bufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore);
binding.Texture.SetStorage(range); binding.Texture.SetStorage(range);
// The texture must be rebound to use the new storage if it was updated. // The texture must be rebound to use the new storage if it was updated.
@ -526,7 +526,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferTextureArrays) foreach (var binding in _bufferTextureArrays)
{ {
var range = bufferCache.GetBufferRange(binding.Range); var range = bufferCache.GetBufferRange(binding.Range, BufferStage.None);
binding.Texture.SetStorage(range); binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture; textureArray[0] = binding.Texture;
@ -536,7 +536,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferImageArrays) foreach (var binding in _bufferImageArrays)
{ {
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
var range = bufferCache.GetBufferRange(binding.Range, isStore); var range = bufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore);
binding.Texture.SetStorage(range); binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture; textureArray[0] = binding.Texture;
@ -565,7 +565,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (!_indexBuffer.Range.IsUnmapped) if (!_indexBuffer.Range.IsUnmapped)
{ {
BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range); BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer);
_context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type); _context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type);
} }
@ -597,7 +597,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue; continue;
} }
BufferRange buffer = bufferCache.GetBufferRange(vb.Range); BufferRange buffer = bufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor); vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
} }
@ -637,7 +637,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue; continue;
} }
tfbs[index] = bufferCache.GetBufferRange(tfb.Range, write: true); tfbs[index] = bufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
} }
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs); _context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
@ -684,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset); _context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, write: true)); buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true));
} }
} }
@ -751,6 +751,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++) for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
{ {
ref var buffers = ref bindings[(int)stage - 1]; ref var buffers = ref bindings[(int)stage - 1];
BufferStage bufferStage = BufferStageUtils.FromShaderStage(stage);
for (int index = 0; index < buffers.Count; index++) for (int index = 0; index < buffers.Count; index++)
{ {
@ -762,8 +763,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write); var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
var range = isStorage var range = isStorage
? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite) ? bufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
: bufferCache.GetBufferRange(bounds.Range); : bufferCache.GetBufferRange(bounds.Range, bufferStage);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range); ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
} }
@ -799,8 +800,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write); var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
var range = isStorage var range = isStorage
? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite) ? bufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
: bufferCache.GetBufferRange(bounds.Range); : bufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range); ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
} }
@ -875,7 +876,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
Format format, Format format,
bool isImage) bool isImage)
{ {
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range); _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage)); _bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage));
} }
@ -883,6 +884,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings. /// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings.
/// </summary> /// </summary>
/// <param name="stage">Shader stage accessing the texture</param>
/// <param name="array">Texture array where the element will be inserted</param> /// <param name="array">Texture array where the element will be inserted</param>
/// <param name="texture">Buffer texture</param> /// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param> /// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
@ -890,6 +892,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="index">Index of the binding on the array</param> /// <param name="index">Index of the binding on the array</param>
/// <param name="format">Format of the buffer texture</param> /// <param name="format">Format of the buffer texture</param>
public void SetBufferTextureStorage( public void SetBufferTextureStorage(
ShaderStage stage,
ITextureArray array, ITextureArray array,
ITexture texture, ITexture texture,
MultiRange range, MultiRange range,
@ -897,7 +900,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
int index, int index,
Format format) Format format)
{ {
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range); _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index, format)); _bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index, format));
} }
@ -905,6 +908,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings. /// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings.
/// </summary> /// </summary>
/// <param name="stage">Shader stage accessing the texture</param>
/// <param name="array">Image array where the element will be inserted</param> /// <param name="array">Image array where the element will be inserted</param>
/// <param name="texture">Buffer texture</param> /// <param name="texture">Buffer texture</param>
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param> /// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
@ -912,6 +916,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="index">Index of the binding on the array</param> /// <param name="index">Index of the binding on the array</param>
/// <param name="format">Format of the buffer texture</param> /// <param name="format">Format of the buffer texture</param>
public void SetBufferTextureStorage( public void SetBufferTextureStorage(
ShaderStage stage,
IImageArray array, IImageArray array,
ITexture texture, ITexture texture,
MultiRange range, MultiRange range,
@ -919,7 +924,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
int index, int index,
Format format) Format format)
{ {
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range); _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index, format)); _bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index, format));
} }

View File

@ -1,37 +1,21 @@
using System; using System;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory namespace Ryujinx.Graphics.Gpu.Memory
{ {
/// <summary> /// <summary>
/// A record of when buffer data was copied from one buffer to another, along with the SyncNumber when the migration will be complete. /// A record of when buffer data was copied from multiple buffers to one migration target,
/// Keeps the source buffer alive for data flushes until the migration is complete. /// along with the SyncNumber when the migration will be complete.
/// Keeps the source buffers alive for data flushes until the migration is complete.
/// All spans cover the full range of the "destination" buffer.
/// </summary> /// </summary>
internal class BufferMigration : IDisposable internal class BufferMigration : IDisposable
{ {
/// <summary> /// <summary>
/// The offset for the migrated region. /// Ranges from source buffers that were copied as part of this migration.
/// Ordered by increasing base address.
/// </summary> /// </summary>
private readonly ulong _offset; public BufferMigrationSpan[] Spans { get; private set; }
/// <summary>
/// The size for the migrated region.
/// </summary>
private readonly ulong _size;
/// <summary>
/// The buffer that was migrated from.
/// </summary>
private readonly Buffer _buffer;
/// <summary>
/// The source range action, to be called on overlap with an unreached sync number.
/// </summary>
private readonly Action<ulong, ulong> _sourceRangeAction;
/// <summary>
/// The source range list.
/// </summary>
private readonly BufferModifiedRangeList _source;
/// <summary> /// <summary>
/// The destination range list. This range list must be updated when flushing the source. /// The destination range list. This range list must be updated when flushing the source.
@ -43,55 +27,193 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </summary> /// </summary>
public readonly ulong SyncNumber; public readonly ulong SyncNumber;
/// <summary>
/// Number of active users there are traversing this migration's spans.
/// </summary>
private int _refCount;
/// <summary>
/// Create a new buffer migration.
/// </summary>
/// <param name="spans">Source spans for the migration</param>
/// <param name="destination">Destination buffer range list</param>
/// <param name="syncNumber">Sync number where this migration will be complete</param>
public BufferMigration(BufferMigrationSpan[] spans, BufferModifiedRangeList destination, ulong syncNumber)
{
Spans = spans;
Destination = destination;
SyncNumber = syncNumber;
}
/// <summary>
/// Add a span to the migration. Allocates a new array with the target size, and replaces it.
/// </summary>
/// <remarks>
/// The base address for the span is assumed to be higher than all other spans in the migration,
/// to keep the span array ordered.
/// </remarks>
public void AddSpanToEnd(BufferMigrationSpan span)
{
BufferMigrationSpan[] oldSpans = Spans;
BufferMigrationSpan[] newSpans = new BufferMigrationSpan[oldSpans.Length + 1];
oldSpans.CopyTo(newSpans, 0);
newSpans[oldSpans.Length] = span;
Spans = newSpans;
}
/// <summary>
/// Performs the given range action, or one from a migration that overlaps and has not synced yet.
/// </summary>
/// <param name="offset">The offset to pass to the action</param>
/// <param name="size">The size to pass to the action</param>
/// <param name="syncNumber">The sync number that has been reached</param>
/// <param name="rangeAction">The action to perform</param>
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction)
{
long syncDiff = (long)(syncNumber - SyncNumber);
if (syncDiff >= 0)
{
// The migration has completed. Run the parent action.
rangeAction(offset, size, syncNumber);
}
else
{
Interlocked.Increment(ref _refCount);
ulong prevAddress = offset;
ulong endAddress = offset + size;
foreach (BufferMigrationSpan span in Spans)
{
if (!span.Overlaps(offset, size))
{
continue;
}
if (span.Address > prevAddress)
{
// There's a gap between this span and the last (or the start address). Flush the range using the parent action.
rangeAction(prevAddress, span.Address - prevAddress, syncNumber);
}
span.RangeActionWithMigration(offset, size, syncNumber);
prevAddress = span.Address + span.Size;
}
if (endAddress > prevAddress)
{
// There's a gap at the end of the range with no migration. Flush the range using the parent action.
rangeAction(prevAddress, endAddress - prevAddress, syncNumber);
}
Interlocked.Decrement(ref _refCount);
}
}
/// <summary>
/// Dispose the buffer migration. This removes the reference from the destination range list,
/// and runs all the dispose buffers for the migration spans. (typically disposes the source buffer)
/// </summary>
public void Dispose()
{
while (Volatile.Read(ref _refCount) > 0)
{
// Coming into this method, the sync for the migration will be met, so nothing can increment the ref count.
// However, an existing traversal of the spans for data flush could still be in progress.
// Spin if this is ever the case, so they don't get disposed before the operation is complete.
}
Destination.RemoveMigration(this);
foreach (BufferMigrationSpan span in Spans)
{
span.Dispose();
}
}
}
/// <summary>
/// A record of when buffer data was copied from one buffer to another, for a specific range in a source buffer.
/// Keeps the source buffer alive for data flushes until the migration is complete.
/// </summary>
internal readonly struct BufferMigrationSpan : IDisposable
{
/// <summary>
/// The offset for the migrated region.
/// </summary>
public readonly ulong Address;
/// <summary>
/// The size for the migrated region.
/// </summary>
public readonly ulong Size;
/// <summary>
/// The action to perform when the migration isn't needed anymore.
/// </summary>
private readonly Action _disposeAction;
/// <summary>
/// The source range action, to be called on overlap with an unreached sync number.
/// </summary>
private readonly BufferFlushAction _sourceRangeAction;
/// <summary>
/// Optional migration for the source data. Can chain together if many migrations happen in a short time.
/// If this is null, then _sourceRangeAction will always provide up to date data.
/// </summary>
private readonly BufferMigration _source;
/// <summary> /// <summary>
/// Creates a record for a buffer migration. /// Creates a record for a buffer migration.
/// </summary> /// </summary>
/// <param name="buffer">The source buffer for this migration</param> /// <param name="buffer">The source buffer for this migration</param>
/// <param name="disposeAction">The action to perform when the migration isn't needed anymore</param>
/// <param name="sourceRangeAction">The flush action for the source buffer</param> /// <param name="sourceRangeAction">The flush action for the source buffer</param>
/// <param name="source">The modified range list for the source buffer</param> /// <param name="source">Pending migration for the source buffer</param>
/// <param name="dest">The modified range list for the destination buffer</param> public BufferMigrationSpan(
/// <param name="syncNumber">The sync number for when the migration is complete</param>
public BufferMigration(
Buffer buffer, Buffer buffer,
Action<ulong, ulong> sourceRangeAction, Action disposeAction,
BufferModifiedRangeList source, BufferFlushAction sourceRangeAction,
BufferModifiedRangeList dest, BufferMigration source)
ulong syncNumber)
{ {
_offset = buffer.Address; Address = buffer.Address;
_size = buffer.Size; Size = buffer.Size;
_buffer = buffer; _disposeAction = disposeAction;
_sourceRangeAction = sourceRangeAction; _sourceRangeAction = sourceRangeAction;
_source = source; _source = source;
Destination = dest;
SyncNumber = syncNumber;
} }
/// <summary>
/// Creates a record for a buffer migration, using the default buffer dispose action.
/// </summary>
/// <param name="buffer">The source buffer for this migration</param>
/// <param name="sourceRangeAction">The flush action for the source buffer</param>
/// <param name="source">Pending migration for the source buffer</param>
public BufferMigrationSpan(
Buffer buffer,
BufferFlushAction sourceRangeAction,
BufferMigration source) : this(buffer, buffer.DecrementReferenceCount, sourceRangeAction, source) { }
/// <summary> /// <summary>
/// Determine if the given range overlaps this migration, and has not been completed yet. /// Determine if the given range overlaps this migration, and has not been completed yet.
/// </summary> /// </summary>
/// <param name="offset">Start offset</param> /// <param name="offset">Start offset</param>
/// <param name="size">Range size</param> /// <param name="size">Range size</param>
/// <param name="syncNumber">The sync number that was waited on</param>
/// <returns>True if overlapping and in progress, false otherwise</returns> /// <returns>True if overlapping and in progress, false otherwise</returns>
public bool Overlaps(ulong offset, ulong size, ulong syncNumber) public bool Overlaps(ulong offset, ulong size)
{ {
ulong end = offset + size; ulong end = offset + size;
ulong destEnd = _offset + _size; ulong destEnd = Address + Size;
long syncDiff = (long)(syncNumber - SyncNumber); // syncNumber is less if the copy has not completed.
return !(end <= _offset || offset >= destEnd) && syncDiff < 0; return !(end <= Address || offset >= destEnd);
}
/// <summary>
/// Determine if the given range matches this migration.
/// </summary>
/// <param name="offset">Start offset</param>
/// <param name="size">Range size</param>
/// <returns>True if the range exactly matches, false otherwise</returns>
public bool FullyMatches(ulong offset, ulong size)
{
return _offset == offset && _size == size;
} }
/// <summary> /// <summary>
@ -100,26 +222,30 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="offset">Start offset</param> /// <param name="offset">Start offset</param>
/// <param name="size">Range size</param> /// <param name="size">Range size</param>
/// <param name="syncNumber">Current sync number</param> /// <param name="syncNumber">Current sync number</param>
/// <param name="parent">The modified range list that originally owned this range</param> public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber)
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent)
{ {
ulong end = offset + size; ulong end = offset + size;
end = Math.Min(_offset + _size, end); end = Math.Min(Address + Size, end);
offset = Math.Max(_offset, offset); offset = Math.Max(Address, offset);
size = end - offset; size = end - offset;
_source.RangeActionWithMigration(offset, size, syncNumber, parent, _sourceRangeAction); if (_source != null)
{
_source.RangeActionWithMigration(offset, size, syncNumber, _sourceRangeAction);
}
else
{
_sourceRangeAction(offset, size, syncNumber);
}
} }
/// <summary> /// <summary>
/// Removes this reference to the range list, potentially allowing for the source buffer to be disposed. /// Removes this migration span, potentially allowing for the source buffer to be disposed.
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
Destination.RemoveMigration(this); _disposeAction();
_buffer.DecrementReferenceCount();
} }
} }
} }

View File

@ -1,7 +1,6 @@
using Ryujinx.Common.Pools; using Ryujinx.Common.Pools;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Ryujinx.Graphics.Gpu.Memory namespace Ryujinx.Graphics.Gpu.Memory
@ -72,10 +71,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly GpuContext _context; private readonly GpuContext _context;
private readonly Buffer _parent; private readonly Buffer _parent;
private readonly Action<ulong, ulong> _flushAction; private readonly BufferFlushAction _flushAction;
private List<BufferMigration> _sources; private BufferMigration _source;
private BufferMigration _migrationTarget; private BufferModifiedRangeList _migrationTarget;
private readonly object _lock = new(); private readonly object _lock = new();
@ -99,7 +98,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="context">GPU context that the buffer range list belongs to</param> /// <param name="context">GPU context that the buffer range list belongs to</param>
/// <param name="parent">The parent buffer that owns this range list</param> /// <param name="parent">The parent buffer that owns this range list</param>
/// <param name="flushAction">The flush action for the parent buffer</param> /// <param name="flushAction">The flush action for the parent buffer</param>
public BufferModifiedRangeList(GpuContext context, Buffer parent, Action<ulong, ulong> flushAction) : base(BackingInitialSize) public BufferModifiedRangeList(GpuContext context, Buffer parent, BufferFlushAction flushAction) : base(BackingInitialSize)
{ {
_context = context; _context = context;
_parent = parent; _parent = parent;
@ -199,6 +198,36 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
} }
/// <summary>
/// Gets modified ranges within the specified region, and then fires the given action for each range individually.
/// </summary>
/// <param name="address">Start address to query</param>
/// <param name="size">Size to query</param>
/// <param name="syncNumber">Sync number required for a range to be signalled</param>
/// <param name="rangeAction">The action to call for each modified range</param>
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
{
int count = 0;
ref var overlaps = ref ThreadStaticArray<BufferModifiedRange>.Get();
// Range list must be consistent for this operation.
lock (_lock)
{
count = FindOverlapsNonOverlapping(address, size, ref overlaps);
}
for (int i = 0; i < count; i++)
{
BufferModifiedRange overlap = overlaps[i];
if (overlap.SyncNumber == syncNumber)
{
rangeAction(overlap.Address, overlap.Size);
}
}
}
/// <summary> /// <summary>
/// Gets modified ranges within the specified region, and then fires the given action for each range individually. /// Gets modified ranges within the specified region, and then fires the given action for each range individually.
/// </summary> /// </summary>
@ -245,41 +274,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="offset">The offset to pass to the action</param> /// <param name="offset">The offset to pass to the action</param>
/// <param name="size">The size to pass to the action</param> /// <param name="size">The size to pass to the action</param>
/// <param name="syncNumber">The sync number that has been reached</param> /// <param name="syncNumber">The sync number that has been reached</param>
/// <param name="parent">The modified range list that originally owned this range</param>
/// <param name="rangeAction">The action to perform</param> /// <param name="rangeAction">The action to perform</param>
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent, Action<ulong, ulong> rangeAction) public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction)
{ {
bool firstSource = true; if (_source != null)
if (parent != this)
{ {
lock (_lock) _source.RangeActionWithMigration(offset, size, syncNumber, rangeAction);
{
if (_sources != null)
{
foreach (BufferMigration source in _sources)
{
if (source.Overlaps(offset, size, syncNumber))
{
if (firstSource && !source.FullyMatches(offset, size))
{
// Perform this buffer's action first. The migrations will run after.
rangeAction(offset, size);
}
source.RangeActionWithMigration(offset, size, syncNumber, parent);
firstSource = false;
}
}
}
}
} }
else
if (firstSource)
{ {
// No overlapping migrations, or they are not meant for this range, flush the data using the given action. rangeAction(offset, size, syncNumber);
rangeAction(offset, size);
} }
} }
@ -319,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ClearPart(overlap, clampAddress, clampEnd); ClearPart(overlap, clampAddress, clampEnd);
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, overlap.Parent, _flushAction); RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
} }
} }
@ -329,7 +333,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
// There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine. // There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine.
_migrationTarget.Destination.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); _migrationTarget.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
} }
/// <summary> /// <summary>
@ -367,7 +371,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (rangeCount == -1) if (rangeCount == -1)
{ {
_migrationTarget.Destination.WaitForAndFlushRanges(address, size); _migrationTarget.WaitForAndFlushRanges(address, size);
return; return;
} }
@ -407,6 +411,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary> /// <summary>
/// Inherit ranges from another modified range list. /// Inherit ranges from another modified range list.
/// </summary> /// </summary>
/// <remarks>
/// Assumes that ranges will be inherited in address ascending order.
/// </remarks>
/// <param name="ranges">The range list to inherit from</param> /// <param name="ranges">The range list to inherit from</param>
/// <param name="registerRangeAction">The action to call for each modified range</param> /// <param name="registerRangeAction">The action to call for each modified range</param>
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction) public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
@ -415,18 +422,31 @@ namespace Ryujinx.Graphics.Gpu.Memory
lock (ranges._lock) lock (ranges._lock)
{ {
BufferMigration migration = new(ranges._parent, ranges._flushAction, ranges, this, _context.SyncNumber);
ranges._parent.IncrementReferenceCount();
ranges._migrationTarget = migration;
_context.RegisterBufferMigration(migration);
inheritRanges = ranges.ToArray(); inheritRanges = ranges.ToArray();
lock (_lock) lock (_lock)
{ {
(_sources ??= new List<BufferMigration>()).Add(migration); // Copy over the migration from the previous range list
BufferMigration oldMigration = ranges._source;
BufferMigrationSpan span = new BufferMigrationSpan(ranges._parent, ranges._flushAction, oldMigration);
ranges._parent.IncrementReferenceCount();
if (_source == null)
{
// Create a new migration.
_source = new BufferMigration(new BufferMigrationSpan[] { span }, this, _context.SyncNumber);
_context.RegisterBufferMigration(_source);
}
else
{
// Extend the migration
_source.AddSpanToEnd(span);
}
ranges._migrationTarget = this;
foreach (BufferModifiedRange range in inheritRanges) foreach (BufferModifiedRange range in inheritRanges)
{ {
@ -445,6 +465,27 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
} }
/// <summary>
/// Register a migration from previous buffer storage. This migration is from a snapshot of the buffer's
/// current handle to its handle in the future, and is assumed to be complete when the sync action completes.
/// When the migration completes, the handle is disposed.
/// </summary>
public void SelfMigration()
{
lock (_lock)
{
BufferMigrationSpan span = new(_parent, _parent.GetSnapshotDisposeAction(), _parent.GetSnapshotFlushAction(), _source);
BufferMigration migration = new(new BufferMigrationSpan[] { span }, this, _context.SyncNumber);
// Migration target is used to redirect flush actions to the latest range list,
// so we don't need to set it here. (this range list is still the latest)
_context.RegisterBufferMigration(migration);
_source = migration;
}
}
/// <summary> /// <summary>
/// Removes a source buffer migration, indicating its copy has completed. /// Removes a source buffer migration, indicating its copy has completed.
/// </summary> /// </summary>
@ -453,7 +494,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
{ {
lock (_lock) lock (_lock)
{ {
_sources.Remove(migration); if (_source == migration)
{
_source = null;
}
} }
} }

View File

@ -0,0 +1,295 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using System;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
/// Manages flushing ranges from buffers in advance for easy access, if they are flushed often.
/// Typically, from device local memory to a host mapped target for cached access.
/// </summary>
internal class BufferPreFlush : IDisposable
{
private const ulong PageSize = MemoryManager.PageSize;
/// <summary>
/// Threshold for the number of copies without a flush required to disable preflush on a page.
/// </summary>
private const int DeactivateCopyThreshold = 200;
/// <summary>
/// Value that indicates whether a page has been flushed or copied before.
/// </summary>
private enum PreFlushState
{
None,
HasFlushed,
HasCopied
}
/// <summary>
/// Flush state for each page of the buffer.
/// Controls whether data should be copied to the flush buffer, what sync is expected
/// and unflushed copy counting for stopping copies that are no longer needed.
/// </summary>
private struct PreFlushPage
{
public PreFlushState State;
public ulong FirstActivatedSync;
public ulong LastCopiedSync;
public int CopyCount;
}
/// <summary>
/// True if there are ranges that should copy to the flush buffer, false otherwise.
/// </summary>
public bool ShouldCopy { get; private set; }
private readonly GpuContext _context;
private readonly Buffer _buffer;
private readonly PreFlushPage[] _pages;
private readonly ulong _address;
private readonly ulong _size;
private readonly ulong _misalignment;
private readonly Action<BufferHandle, ulong, ulong> _flushAction;
private BufferHandle _flushBuffer;
public BufferPreFlush(GpuContext context, Buffer parent, Action<BufferHandle, ulong, ulong> flushAction)
{
_context = context;
_buffer = parent;
_address = parent.Address;
_size = parent.Size;
_pages = new PreFlushPage[BitUtils.DivRoundUp(_size, PageSize)];
_misalignment = _address & (PageSize - 1);
_flushAction = flushAction;
}
/// <summary>
/// Ensure that the flush buffer exists.
/// </summary>
private void EnsureFlushBuffer()
{
if (_flushBuffer == BufferHandle.Null)
{
_flushBuffer = _context.Renderer.CreateBuffer((int)_size, BufferAccess.HostMemory);
}
}
/// <summary>
/// Gets a page range from an address and size byte range.
/// </summary>
/// <param name="address">Range address</param>
/// <param name="size">Range size</param>
/// <returns>A page index and count</returns>
private (int index, int count) GetPageRange(ulong address, ulong size)
{
ulong offset = address - _address;
ulong endOffset = offset + size;
int basePage = (int)(offset / PageSize);
int endPage = (int)((endOffset - 1) / PageSize);
return (basePage, 1 + endPage - basePage);
}
/// <summary>
/// Gets an offset and size range in the parent buffer from a page index and count.
/// </summary>
/// <param name="startPage">Range start page</param>
/// <param name="count">Range page count</param>
/// <returns>Offset and size range</returns>
private (int offset, int size) GetOffset(int startPage, int count)
{
int offset = (int)((ulong)startPage * PageSize - _misalignment);
int endOffset = (int)((ulong)(startPage + count) * PageSize - _misalignment);
offset = Math.Max(0, offset);
endOffset = Math.Min((int)_size, endOffset);
return (offset, endOffset - offset);
}
/// <summary>
/// Copy a range of pages from the parent buffer into the flush buffer.
/// </summary>
/// <param name="startPage">Range start page</param>
/// <param name="count">Range page count</param>
private void CopyPageRange(int startPage, int count)
{
(int offset, int size) = GetOffset(startPage, count);
EnsureFlushBuffer();
_context.Renderer.Pipeline.CopyBuffer(_buffer.Handle, _flushBuffer, offset, offset, size);
}
/// <summary>
/// Copy a modified range into the flush buffer if it's marked as flushed.
/// Any pages the range overlaps are copied, and copies aren't repeated in the same sync number.
/// </summary>
/// <param name="address">Range address</param>
/// <param name="size">Range size</param>
public void CopyModified(ulong address, ulong size)
{
(int baseIndex, int count) = GetPageRange(address, size);
ulong syncNumber = _context.SyncNumber;
int startPage = -1;
for (int i = 0; i < count; i++)
{
int pageIndex = baseIndex + i;
ref PreFlushPage page = ref _pages[pageIndex];
if (page.State > PreFlushState.None)
{
// Perform the copy, and update the state of each page.
if (startPage == -1)
{
startPage = pageIndex;
}
if (page.State != PreFlushState.HasCopied)
{
page.FirstActivatedSync = syncNumber;
page.State = PreFlushState.HasCopied;
}
else if (page.CopyCount++ >= DeactivateCopyThreshold)
{
page.CopyCount = 0;
page.State = PreFlushState.None;
}
if (page.LastCopiedSync != syncNumber)
{
page.LastCopiedSync = syncNumber;
}
}
else if (startPage != -1)
{
CopyPageRange(startPage, pageIndex - startPage);
startPage = -1;
}
}
if (startPage != -1)
{
CopyPageRange(startPage, (baseIndex + count) - startPage);
}
}
/// <summary>
/// Flush the given page range back into guest memory, optionally using data from the flush buffer.
/// The actual flushed range is an intersection of the page range and the address range.
/// </summary>
/// <param name="address">Address range start</param>
/// <param name="size">Address range size</param>
/// <param name="startPage">Page range start</param>
/// <param name="count">Page range count</param>
/// <param name="preFlush">True if the data should come from the flush buffer</param>
private void FlushPageRange(ulong address, ulong size, int startPage, int count, bool preFlush)
{
(int pageOffset, int pageSize) = GetOffset(startPage, count);
int offset = (int)(address - _address);
int end = offset + (int)size;
offset = Math.Max(offset, pageOffset);
end = Math.Min(end, pageOffset + pageSize);
if (end >= offset)
{
BufferHandle handle = preFlush ? _flushBuffer : _buffer.Handle;
_flushAction(handle, _address + (ulong)offset, (ulong)(end - offset));
}
}
/// <summary>
/// Flush the given address range back into guest memory, optionally using data from the flush buffer.
/// When a copy has been performed on or before the waited sync number, the data can come from the flush buffer.
/// Otherwise, it flushes the parent buffer directly.
/// </summary>
/// <param name="address">Range address</param>
/// <param name="size">Range size</param>
/// <param name="syncNumber">Sync number that has been waited for</param>
public void FlushWithAction(ulong address, ulong size, ulong syncNumber)
{
// Copy the parts of the range that have pre-flush copies that have been completed.
// Run the flush action for ranges that don't have pre-flush copies.
// If a range doesn't have a pre-flush copy, consider adding one.
(int baseIndex, int count) = GetPageRange(address, size);
bool rangePreFlushed = false;
int startPage = -1;
for (int i = 0; i < count; i++)
{
int pageIndex = baseIndex + i;
ref PreFlushPage page = ref _pages[pageIndex];
bool flushPage = false;
page.CopyCount = 0;
if (page.State == PreFlushState.HasCopied)
{
if (syncNumber >= page.FirstActivatedSync)
{
// After the range is first activated, its data will always be copied to the preflush buffer on each sync.
flushPage = true;
}
}
else if (page.State == PreFlushState.None)
{
page.State = PreFlushState.HasFlushed;
ShouldCopy = true;
}
if (flushPage)
{
if (!rangePreFlushed || startPage == -1)
{
if (startPage != -1)
{
FlushPageRange(address, size, startPage, pageIndex - startPage, false);
}
rangePreFlushed = true;
startPage = pageIndex;
}
}
else if (rangePreFlushed || startPage == -1)
{
if (startPage != -1)
{
FlushPageRange(address, size, startPage, pageIndex - startPage, true);
}
rangePreFlushed = false;
startPage = pageIndex;
}
}
if (startPage != -1)
{
FlushPageRange(address, size, startPage, (baseIndex + count) - startPage, rangePreFlushed);
}
}
/// <summary>
/// Dispose the flush buffer, if present.
/// </summary>
public void Dispose()
{
if (_flushBuffer != BufferHandle.Null)
{
_context.Renderer.DeleteBuffer(_flushBuffer);
}
}
}
}

View File

@ -0,0 +1,99 @@
using Ryujinx.Graphics.Shader;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
/// Pipeline stages that can modify buffer data, as well as flags indicating storage usage.
/// Must match ShaderStage for the shader stages, though anything after that can be in any order.
/// </summary>
internal enum BufferStage : byte
{
Compute,
Vertex,
TessellationControl,
TessellationEvaluation,
Geometry,
Fragment,
Indirect,
VertexBuffer,
IndexBuffer,
Copy,
TransformFeedback,
Internal,
None,
StageMask = 0x3f,
StorageMask = 0xc0,
StorageRead = 0x40,
StorageWrite = 0x80,
#pragma warning disable CA1069 // Enums values should not be duplicated
StorageAtomic = 0xc0
#pragma warning restore CA1069 // Enums values should not be duplicated
}
/// <summary>
/// Utility methods to convert shader stages and binding flags into buffer stages.
/// </summary>
internal static class BufferStageUtils
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage FromShaderStage(ShaderStage stage)
{
return (BufferStage)stage;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage FromShaderStage(int stageIndex)
{
return (BufferStage)(stageIndex + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage FromUsage(BufferUsageFlags flags)
{
if (flags.HasFlag(BufferUsageFlags.Write))
{
return BufferStage.StorageWrite;
}
else
{
return BufferStage.StorageRead;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage FromUsage(TextureUsageFlags flags)
{
if (flags.HasFlag(TextureUsageFlags.ImageStore))
{
return BufferStage.StorageWrite;
}
else
{
return BufferStage.StorageRead;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage TextureBuffer(ShaderStage shaderStage, TextureUsageFlags flags)
{
return FromShaderStage(shaderStage) | FromUsage(flags);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage GraphicsStorage(int stageIndex, BufferUsageFlags flags)
{
return FromShaderStage(stageIndex) | FromUsage(flags);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferStage ComputeStorage(BufferUsageFlags flags)
{
return BufferStage.Compute | FromUsage(flags);
}
}
}

View File

@ -2,7 +2,6 @@ using Ryujinx.Common.Memory;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -242,9 +241,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size); MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size);
GetSpan(va, size).CopyTo(memoryOwner.Memory.Span); ReadImpl(va, memoryOwner.Span, tracked);
return new WritableRegion(this, va, memoryOwner, tracked); return new WritableRegion(this, va, memoryOwner, tracked);
} }

View File

@ -192,9 +192,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
else else
{ {
IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(range.GetSize()); MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(checked((int)range.GetSize()));
Memory<byte> memory = memoryOwner.Memory; Span<byte> memorySpan = memoryOwner.Span;
int offset = 0; int offset = 0;
for (int i = 0; i < range.Count; i++) for (int i = 0; i < range.Count; i++)
@ -203,7 +203,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
int size = (int)currentRange.Size; int size = (int)currentRange.Size;
if (currentRange.Address != MemoryManager.PteUnmapped) if (currentRange.Address != MemoryManager.PteUnmapped)
{ {
GetSpan(currentRange.Address, size).CopyTo(memory.Span.Slice(offset, size)); GetSpan(currentRange.Address, size).CopyTo(memorySpan.Slice(offset, size));
} }
offset += size; offset += size;
} }

View File

@ -62,6 +62,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
var result = new TextureBindingInfo( var result = new TextureBindingInfo(
target, target,
descriptor.Set,
descriptor.Binding, descriptor.Binding,
descriptor.ArrayLength, descriptor.ArrayLength,
descriptor.CbufSlot, descriptor.CbufSlot,
@ -90,6 +91,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
var result = new TextureBindingInfo( var result = new TextureBindingInfo(
target, target,
format, format,
descriptor.Set,
descriptor.Binding, descriptor.Binding,
descriptor.ArrayLength, descriptor.ArrayLength,
descriptor.CbufSlot, descriptor.CbufSlot,

View File

@ -125,9 +125,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
CompressionAlgorithm algorithm = CompressionAlgorithm.None; CompressionAlgorithm algorithm = CompressionAlgorithm.None;
Read(ref algorithm); Read(ref algorithm);
if (algorithm == CompressionAlgorithm.Deflate) switch (algorithm)
{ {
_activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true); case CompressionAlgorithm.None:
break;
case CompressionAlgorithm.Deflate:
_activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true);
break;
case CompressionAlgorithm.Brotli:
_activeStream = new BrotliStream(_stream, CompressionMode.Decompress, true);
break;
default:
throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\"");
} }
} }
@ -139,9 +148,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{ {
Write(ref algorithm); Write(ref algorithm);
if (algorithm == CompressionAlgorithm.Deflate) switch (algorithm)
{ {
_activeStream = new DeflateStream(_stream, CompressionLevel.Fastest, true); case CompressionAlgorithm.None:
break;
case CompressionAlgorithm.Deflate:
_activeStream = new DeflateStream(_stream, CompressionLevel.Fastest, true);
break;
case CompressionAlgorithm.Brotli:
_activeStream = new BrotliStream(_stream, CompressionLevel.Fastest, true);
break;
default:
throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\"");
} }
} }
@ -177,7 +195,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
switch (algorithm) switch (algorithm)
{ {
case CompressionAlgorithm.None: case CompressionAlgorithm.None:
stream.Read(data); stream.ReadExactly(data);
break; break;
case CompressionAlgorithm.Deflate: case CompressionAlgorithm.Deflate:
stream = new DeflateStream(stream, CompressionMode.Decompress, true); stream = new DeflateStream(stream, CompressionMode.Decompress, true);
@ -187,6 +205,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
} }
stream.Dispose(); stream.Dispose();
break; break;
case CompressionAlgorithm.Brotli:
stream = new BrotliStream(stream, CompressionMode.Decompress, true);
for (int offset = 0; offset < data.Length;)
{
offset += stream.Read(data[offset..]);
}
stream.Dispose();
break;
} }
} }
@ -210,6 +236,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
stream.Write(data); stream.Write(data);
stream.Dispose(); stream.Dispose();
break; break;
case CompressionAlgorithm.Brotli:
stream = new BrotliStream(stream, CompressionLevel.Fastest, true);
stream.Write(data);
stream.Dispose();
break;
} }
} }
} }

View File

@ -14,5 +14,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// Deflate compression (RFC 1951). /// Deflate compression (RFC 1951).
/// </summary> /// </summary>
Deflate, Deflate,
/// <summary>
/// Brotli compression (RFC 7932).
/// </summary>
Brotli,
} }
} }

View File

@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <returns>Compression algorithm</returns> /// <returns>Compression algorithm</returns>
public static CompressionAlgorithm GetCompressionAlgorithm() public static CompressionAlgorithm GetCompressionAlgorithm()
{ {
return CompressionAlgorithm.Deflate; return CompressionAlgorithm.Brotli;
} }
} }
} }

View File

@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
} }
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin); dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
dataFileStream.Read(cb1Data); dataFileStream.ReadExactly(cb1Data);
BinarySerializer.ReadCompressed(dataFileStream, guestCode); BinarySerializer.ReadCompressed(dataFileStream, guestCode);
_cache[index] = (guestCode, cb1Data); _cache[index] = (guestCode, cb1Data);
@ -279,7 +279,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin); dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
byte[] cachedCode = new byte[entry.CodeSize]; byte[] cachedCode = new byte[entry.CodeSize];
byte[] cachedCb1Data = new byte[entry.Cb1DataSize]; byte[] cachedCb1Data = new byte[entry.Cb1DataSize];
dataFileStream.Read(cachedCb1Data); dataFileStream.ReadExactly(cachedCb1Data);
BinarySerializer.ReadCompressed(dataFileStream, cachedCode); BinarySerializer.ReadCompressed(dataFileStream, cachedCode);
if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data)) if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data))

View File

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 5936; private const uint CodeGenVersion = 7131;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View File

@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_reservedImages = rrc.ReservedImages; _reservedImages = rrc.ReservedImages;
} }
public int CreateConstantBufferBinding(int index) public SetBindingPair CreateConstantBufferBinding(int index)
{ {
int binding; int binding;
@ -64,10 +64,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
binding = _resourceCounts.UniformBuffersCount++; binding = _resourceCounts.UniformBuffersCount++;
} }
return binding + _reservedConstantBuffers; return new SetBindingPair(_context.Capabilities.UniformBufferSetIndex, binding + _reservedConstantBuffers);
} }
public int CreateImageBinding(int count, bool isBuffer) public SetBindingPair CreateImageBinding(int count, bool isBuffer)
{ {
int binding; int binding;
@ -96,10 +96,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
_resourceCounts.ImagesCount += count; _resourceCounts.ImagesCount += count;
} }
return binding + _reservedImages; return new SetBindingPair(_context.Capabilities.ImageSetIndex, binding + _reservedImages);
} }
public int CreateStorageBufferBinding(int index) public SetBindingPair CreateStorageBufferBinding(int index)
{ {
int binding; int binding;
@ -112,10 +112,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
binding = _resourceCounts.StorageBuffersCount++; binding = _resourceCounts.StorageBuffersCount++;
} }
return binding + _reservedStorageBuffers; return new SetBindingPair(_context.Capabilities.StorageBufferSetIndex, binding + _reservedStorageBuffers);
} }
public int CreateTextureBinding(int count, bool isBuffer) public SetBindingPair CreateTextureBinding(int count, bool isBuffer)
{ {
int binding; int binding;
@ -144,7 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_resourceCounts.TexturesCount += count; _resourceCounts.TexturesCount += count;
} }
return binding + _reservedTextures; return new SetBindingPair(_context.Capabilities.TextureSetIndex, binding + _reservedTextures);
} }
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName) private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
@ -183,6 +183,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
return maxPerStage * Constants.ShaderStages; return maxPerStage * Constants.ShaderStages;
} }
public int CreateExtraSet()
{
if (_resourceCounts.SetsCount >= _context.Capabilities.MaximumExtraSets)
{
return -1;
}
return _context.Capabilities.ExtraSetBaseIndex + _resourceCounts.SetsCount++;
}
public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision; public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision;
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision; public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;

View File

@ -24,5 +24,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Total of images used by the shaders. /// Total of images used by the shaders.
/// </summary> /// </summary>
public int ImagesCount; public int ImagesCount;
/// <summary>
/// Total of extra sets used by the shaders.
/// </summary>
public int SetsCount;
} }
} }

View File

@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Shader namespace Ryujinx.Graphics.Gpu.Shader
@ -9,13 +10,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
class ShaderInfoBuilder class ShaderInfoBuilder
{ {
private const int TotalSets = 4;
private const int UniformSetIndex = 0;
private const int StorageSetIndex = 1;
private const int TextureSetIndex = 2;
private const int ImageSetIndex = 3;
private const ResourceStages SupportBufferStages = private const ResourceStages SupportBufferStages =
ResourceStages.Compute | ResourceStages.Compute |
ResourceStages.Vertex | ResourceStages.Vertex |
@ -36,8 +30,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly int _reservedTextures; private readonly int _reservedTextures;
private readonly int _reservedImages; private readonly int _reservedImages;
private readonly List<ResourceDescriptor>[] _resourceDescriptors; private List<ResourceDescriptor>[] _resourceDescriptors;
private readonly List<ResourceUsage>[] _resourceUsages; private List<ResourceUsage>[] _resourceUsages;
/// <summary> /// <summary>
/// Creates a new shader info builder. /// Creates a new shader info builder.
@ -51,17 +45,27 @@ namespace Ryujinx.Graphics.Gpu.Shader
_fragmentOutputMap = -1; _fragmentOutputMap = -1;
_resourceDescriptors = new List<ResourceDescriptor>[TotalSets]; int uniformSetIndex = context.Capabilities.UniformBufferSetIndex;
_resourceUsages = new List<ResourceUsage>[TotalSets]; int storageSetIndex = context.Capabilities.StorageBufferSetIndex;
int textureSetIndex = context.Capabilities.TextureSetIndex;
int imageSetIndex = context.Capabilities.ImageSetIndex;
for (int index = 0; index < TotalSets; index++) int totalSets = Math.Max(uniformSetIndex, storageSetIndex);
totalSets = Math.Max(totalSets, textureSetIndex);
totalSets = Math.Max(totalSets, imageSetIndex);
totalSets++;
_resourceDescriptors = new List<ResourceDescriptor>[totalSets];
_resourceUsages = new List<ResourceUsage>[totalSets];
for (int index = 0; index < totalSets; index++)
{ {
_resourceDescriptors[index] = new(); _resourceDescriptors[index] = new();
_resourceUsages[index] = new(); _resourceUsages[index] = new();
} }
AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1); AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1);
AddUsage(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1); AddUsage(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1);
ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute); ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute);
@ -73,16 +77,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
// TODO: Handle that better? Maybe we should only set the binding that are really needed on each shader. // TODO: Handle that better? Maybe we should only set the binding that are really needed on each shader.
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages; ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, UniformSetIndex, 1, rrc.ReservedConstantBuffers - 1); PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, uniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, StorageSetIndex, 0, rrc.ReservedStorageBuffers); PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers, true);
PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, TextureSetIndex, 0, rrc.ReservedTextures); PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures);
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, ImageSetIndex, 0, rrc.ReservedImages); PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages, true);
} }
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count) /// <summary>
/// Populates descriptors and usages for vertex as compute and transform feedback emulation reserved resources.
/// </summary>
/// <param name="stages">Shader stages where the resources are used</param>
/// <param name="type">Resource type</param>
/// <param name="setIndex">Resource set index where the resources are used</param>
/// <param name="start">First binding number</param>
/// <param name="count">Amount of bindings</param>
/// <param name="write">True if the binding is written from the shader, false otherwise</param>
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count, bool write = false)
{ {
AddDescriptor(stages, type, setIndex, start, count); AddDescriptor(stages, type, setIndex, start, count);
AddUsage(stages, type, setIndex, start, count); AddUsage(stages, type, setIndex, start, count, write);
} }
/// <summary> /// <summary>
@ -127,18 +140,23 @@ namespace Ryujinx.Graphics.Gpu.Shader
int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2; int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2;
int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2; int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2;
AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage); int uniformSetIndex = _context.Capabilities.UniformBufferSetIndex;
AddDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage); int storageSetIndex = _context.Capabilities.StorageBufferSetIndex;
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage); int textureSetIndex = _context.Capabilities.TextureSetIndex;
AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage); int imageSetIndex = _context.Capabilities.ImageSetIndex;
AddArrayDescriptors(info.Textures, stages, TextureSetIndex, isImage: false); AddDescriptor(stages, ResourceType.UniformBuffer, uniformSetIndex, uniformBinding, uniformsPerStage);
AddArrayDescriptors(info.Images, stages, TextureSetIndex, isImage: true); AddDescriptor(stages, ResourceType.StorageBuffer, storageSetIndex, storageBinding, storagesPerStage);
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, textureSetIndex, textureBinding, texturesPerStage);
AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, imageSetIndex, imageBinding, imagesPerStage);
AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false); AddArrayDescriptors(info.Textures, stages, isImage: false);
AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true); AddArrayDescriptors(info.Images, stages, isImage: true);
AddUsage(info.Textures, stages, TextureSetIndex, isImage: false);
AddUsage(info.Images, stages, ImageSetIndex, isImage: true); AddUsage(info.CBuffers, stages, isStorage: false);
AddUsage(info.SBuffers, stages, isStorage: true);
AddUsage(info.Textures, stages, isImage: false);
AddUsage(info.Images, stages, isImage: true);
} }
/// <summary> /// <summary>
@ -177,9 +195,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="textures">Textures to be added</param> /// <param name="textures">Textures to be added</param>
/// <param name="stages">Stages where the textures are used</param> /// <param name="stages">Stages where the textures are used</param>
/// <param name="setIndex">Descriptor set index where the textures will be bound</param>
/// <param name="isImage">True for images, false for textures</param> /// <param name="isImage">True for images, false for textures</param>
private void AddArrayDescriptors(IEnumerable<TextureDescriptor> textures, ResourceStages stages, int setIndex, bool isImage) private void AddArrayDescriptors(IEnumerable<TextureDescriptor> textures, ResourceStages stages, bool isImage)
{ {
foreach (TextureDescriptor texture in textures) foreach (TextureDescriptor texture in textures)
{ {
@ -187,7 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{ {
ResourceType type = GetTextureResourceType(texture, isImage); ResourceType type = GetTextureResourceType(texture, isImage);
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages)); GetDescriptors(texture.Set).Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages));
} }
} }
} }
@ -200,11 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="setIndex">Descriptor set number where the resource will be bound</param> /// <param name="setIndex">Descriptor set number where the resource will be bound</param>
/// <param name="binding">Binding number where the resource will be bound</param> /// <param name="binding">Binding number where the resource will be bound</param>
/// <param name="count">Number of resources bound at the binding location</param> /// <param name="count">Number of resources bound at the binding location</param>
private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count) /// <param name="write">True if the binding is written from the shader, false otherwise</param>
private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count, bool write = false)
{ {
for (int index = 0; index < count; index++) for (int index = 0; index < count; index++)
{ {
_resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages)); _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages, write));
} }
} }
@ -213,17 +231,17 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="buffers">Buffers to be added</param> /// <param name="buffers">Buffers to be added</param>
/// <param name="stages">Stages where the buffers are used</param> /// <param name="stages">Stages where the buffers are used</param>
/// <param name="setIndex">Descriptor set index where the buffers will be bound</param>
/// <param name="isStorage">True for storage buffers, false for uniform buffers</param> /// <param name="isStorage">True for storage buffers, false for uniform buffers</param>
private void AddUsage(IEnumerable<BufferDescriptor> buffers, ResourceStages stages, int setIndex, bool isStorage) private void AddUsage(IEnumerable<BufferDescriptor> buffers, ResourceStages stages, bool isStorage)
{ {
foreach (BufferDescriptor buffer in buffers) foreach (BufferDescriptor buffer in buffers)
{ {
_resourceUsages[setIndex].Add(new ResourceUsage( GetUsages(buffer.Set).Add(new ResourceUsage(
buffer.Binding, buffer.Binding,
1, 1,
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer, isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
stages)); stages,
buffer.Flags.HasFlag(BufferUsageFlags.Write)));
} }
} }
@ -232,18 +250,70 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="textures">Textures to be added</param> /// <param name="textures">Textures to be added</param>
/// <param name="stages">Stages where the textures are used</param> /// <param name="stages">Stages where the textures are used</param>
/// <param name="setIndex">Descriptor set index where the textures will be bound</param>
/// <param name="isImage">True for images, false for textures</param> /// <param name="isImage">True for images, false for textures</param>
private void AddUsage(IEnumerable<TextureDescriptor> textures, ResourceStages stages, int setIndex, bool isImage) private void AddUsage(IEnumerable<TextureDescriptor> textures, ResourceStages stages, bool isImage)
{ {
foreach (TextureDescriptor texture in textures) foreach (TextureDescriptor texture in textures)
{ {
ResourceType type = GetTextureResourceType(texture, isImage); ResourceType type = GetTextureResourceType(texture, isImage);
_resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages)); GetUsages(texture.Set).Add(new ResourceUsage(
texture.Binding,
texture.ArrayLength,
type,
stages,
texture.Flags.HasFlag(TextureUsageFlags.ImageStore)));
} }
} }
/// <summary>
/// Gets the list of resource descriptors for a given set index. A new list will be created if needed.
/// </summary>
/// <param name="setIndex">Resource set index</param>
/// <returns>List of resource descriptors</returns>
private List<ResourceDescriptor> GetDescriptors(int setIndex)
{
if (_resourceDescriptors.Length <= setIndex)
{
int oldLength = _resourceDescriptors.Length;
Array.Resize(ref _resourceDescriptors, setIndex + 1);
for (int index = oldLength; index <= setIndex; index++)
{
_resourceDescriptors[index] = new();
}
}
return _resourceDescriptors[setIndex];
}
/// <summary>
/// Gets the list of resource usages for a given set index. A new list will be created if needed.
/// </summary>
/// <param name="setIndex">Resource set index</param>
/// <returns>List of resource usages</returns>
private List<ResourceUsage> GetUsages(int setIndex)
{
if (_resourceUsages.Length <= setIndex)
{
int oldLength = _resourceUsages.Length;
Array.Resize(ref _resourceUsages, setIndex + 1);
for (int index = oldLength; index <= setIndex; index++)
{
_resourceUsages[index] = new();
}
}
return _resourceUsages[setIndex];
}
/// <summary>
/// Gets a resource type from a texture descriptor.
/// </summary>
/// <param name="texture">Texture descriptor</param>
/// <param name="isImage">Whether the texture is a image texture (writable) or not (sampled)</param>
/// <returns>Resource type</returns>
private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage) private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage)
{ {
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
@ -278,10 +348,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <returns>Shader information</returns> /// <returns>Shader information</returns>
public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false) public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false)
{ {
var descriptors = new ResourceDescriptorCollection[TotalSets]; int totalSets = _resourceDescriptors.Length;
var usages = new ResourceUsageCollection[TotalSets];
for (int index = 0; index < TotalSets; index++) var descriptors = new ResourceDescriptorCollection[totalSets];
var usages = new ResourceUsageCollection[totalSets];
for (int index = 0; index < totalSets; index++)
{ {
descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly()); descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly()); usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());

View File

@ -1,6 +1,5 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
@ -10,11 +9,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
{ {
static class FormatConverter static class FormatConverter
{ {
public unsafe static IMemoryOwner<byte> ConvertS8D24ToD24S8(ReadOnlySpan<byte> data) public unsafe static MemoryOwner<byte> ConvertS8D24ToD24S8(ReadOnlySpan<byte> data)
{ {
IMemoryOwner<byte> outputMemory = ByteMemoryPool.Rent(data.Length); MemoryOwner<byte> outputMemory = MemoryOwner<byte>.Rent(data.Length);
Span<byte> output = outputMemory.Memory.Span; Span<byte> output = outputMemory.Span;
int start = 0; int start = 0;

View File

@ -63,5 +63,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
} }
} }
public void Dispose()
{
}
} }
} }

View File

@ -48,5 +48,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
} }
} }
public void Dispose()
{
}
} }
} }

View File

@ -48,7 +48,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
internalFormat = (SizedInternalFormat)format.PixelInternalFormat; internalFormat = (SizedInternalFormat)format.PixelInternalFormat;
} }
int levels = Info.GetLevelsClamped(); int levels = Info.Levels;
switch (Info.Target) switch (Info.Target)
{ {

View File

@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
pixelInternalFormat = format.PixelInternalFormat; pixelInternalFormat = format.PixelInternalFormat;
} }
int levels = Info.GetLevelsClamped(); int levels = Info.Levels;
GL.TextureView( GL.TextureView(
Handle, Handle,
@ -267,7 +267,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
public unsafe PinnedSpan<byte> GetData() public unsafe PinnedSpan<byte> GetData()
{ {
int size = 0; int size = 0;
int levels = Info.GetLevelsClamped(); int levels = Info.Levels;
for (int level = 0; level < levels; level++) for (int level = 0; level < levels; level++)
{ {
@ -426,7 +426,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
faces = 6; faces = 6;
} }
int levels = Info.GetLevelsClamped(); int levels = Info.Levels;
for (int level = 0; level < levels; level++) for (int level = 0; level < levels; level++)
{ {
@ -716,7 +716,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
int width = Info.Width; int width = Info.Width;
int height = Info.Height; int height = Info.Height;
int depth = Info.Depth; int depth = Info.Depth;
int levels = Info.GetLevelsClamped(); int levels = Info.Levels;
int offset = 0; int offset = 0;

View File

@ -61,7 +61,9 @@ namespace Ryujinx.Graphics.OpenGL
{ {
BufferCount++; BufferCount++;
if (access.HasFlag(GAL.BufferAccess.FlushPersistent)) var memType = access & GAL.BufferAccess.MemoryTypeMask;
if (memType == GAL.BufferAccess.HostMemory)
{ {
BufferHandle handle = Buffer.CreatePersistent(size); BufferHandle handle = Buffer.CreatePersistent(size);
@ -75,11 +77,6 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
public BufferHandle CreateBuffer(int size, GAL.BufferAccess access, BufferHandle storageHint)
{
return CreateBuffer(size, access);
}
public BufferHandle CreateBuffer(nint pointer, int size) public BufferHandle CreateBuffer(nint pointer, int size)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
@ -148,6 +145,7 @@ namespace Ryujinx.Graphics.OpenGL
return new Capabilities( return new Capabilities(
api: TargetApi.OpenGL, api: TargetApi.OpenGL,
vendorName: GpuVendor, vendorName: GpuVendor,
memoryType: SystemMemoryType.BackendManaged,
hasFrontFacingBug: intelWindows, hasFrontFacingBug: intelWindows,
hasVectorIndexingBug: amdWindows, hasVectorIndexingBug: amdWindows,
needsFragmentOutputSpecialization: false, needsFragmentOutputSpecialization: false,
@ -189,6 +187,12 @@ namespace Ryujinx.Graphics.OpenGL
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle, supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters, supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
supportsDepthClipControl: true, supportsDepthClipControl: true,
uniformBufferSetIndex: 0,
storageBufferSetIndex: 1,
textureSetIndex: 2,
imageSetIndex: 3,
extraSetBaseIndex: 0,
maximumExtraSets: 0,
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver? maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
maximumStorageBuffersPerStage: 16, maximumStorageBuffersPerStage: 16,
maximumTexturesPerStage: 32, maximumTexturesPerStage: 32,

View File

@ -963,6 +963,11 @@ namespace Ryujinx.Graphics.OpenGL
(array as ImageArray).Bind(binding); (array as ImageArray).Bind(binding);
} }
public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array)
{
throw new NotSupportedException("OpenGL does not support descriptor sets.");
}
public void SetIndexBuffer(BufferRange buffer, IndexType type) public void SetIndexBuffer(BufferRange buffer, IndexType type)
{ {
_elementsType = type.Convert(); _elementsType = type.Convert();
@ -1312,6 +1317,11 @@ namespace Ryujinx.Graphics.OpenGL
(array as TextureArray).Bind(binding); (array as TextureArray).Bind(binding);
} }
public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array)
{
throw new NotSupportedException("OpenGL does not support descriptor sets.");
}
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers) public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
{ {
if (_tfEnabled) if (_tfEnabled)

View File

@ -4,14 +4,16 @@ namespace Ryujinx.Graphics.Shader
{ {
// New fields should be added to the end of the struct to keep disk shader cache compatibility. // New fields should be added to the end of the struct to keep disk shader cache compatibility.
public readonly int Set;
public readonly int Binding; public readonly int Binding;
public readonly byte Slot; public readonly byte Slot;
public readonly byte SbCbSlot; public readonly byte SbCbSlot;
public readonly ushort SbCbOffset; public readonly ushort SbCbOffset;
public readonly BufferUsageFlags Flags; public readonly BufferUsageFlags Flags;
public BufferDescriptor(int binding, int slot) public BufferDescriptor(int set, int binding, int slot)
{ {
Set = set;
Binding = binding; Binding = binding;
Slot = (byte)slot; Slot = (byte)slot;
SbCbSlot = 0; SbCbSlot = 0;
@ -19,8 +21,9 @@ namespace Ryujinx.Graphics.Shader
Flags = BufferUsageFlags.None; Flags = BufferUsageFlags.None;
} }
public BufferDescriptor(int binding, int slot, int sbCbSlot, int sbCbOffset, BufferUsageFlags flags) public BufferDescriptor(int set, int binding, int slot, int sbCbSlot, int sbCbOffset, BufferUsageFlags flags)
{ {
Set = set;
Binding = binding; Binding = binding;
Slot = (byte)slot; Slot = (byte)slot;
SbCbSlot = (byte)sbCbSlot; SbCbSlot = (byte)sbCbSlot;

View File

@ -462,7 +462,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
} }
else else
{ {
context.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition); context.Properties.Textures.TryGetValue(texOp.GetTextureSetAndBinding(), out TextureDefinition definition);
bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer; bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
string texCall; string texCall;
@ -639,7 +639,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex) private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{ {
TextureDefinition textureDefinition = context.Properties.Textures[texOp.Binding]; TextureDefinition textureDefinition = context.Properties.Textures[texOp.GetTextureSetAndBinding()];
string name = textureDefinition.Name; string name = textureDefinition.Name;
if (textureDefinition.ArrayLength != 1) if (textureDefinition.ArrayLength != 1)
@ -649,7 +649,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (texOp.IsSeparate) if (texOp.IsSeparate)
{ {
TextureDefinition samplerDefinition = context.Properties.Textures[texOp.SamplerBinding]; TextureDefinition samplerDefinition = context.Properties.Textures[texOp.GetSamplerSetAndBinding()];
string samplerName = samplerDefinition.Name; string samplerName = samplerDefinition.Name;
if (samplerDefinition.ArrayLength != 1) if (samplerDefinition.ArrayLength != 1)
@ -665,7 +665,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
private static string GetImageName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex) private static string GetImageName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{ {
TextureDefinition definition = context.Properties.Images[texOp.Binding]; TextureDefinition definition = context.Properties.Images[texOp.GetTextureSetAndBinding()];
string name = definition.Name; string name = definition.Name;
if (definition.ArrayLength != 1) if (definition.ArrayLength != 1)

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