Compare commits

...

37 Commits

Author SHA1 Message Date
gdkchan
9f12e50a54 Refactor attribute handling on the shader generator (#4565)
* Refactor attribute handling on the shader generator

* Implement gl_ViewportMask[]

* Add back the Intel FrontFacing bug workaround

* Fix GLSL transform feedback outputs mistmatch with fragment stage

* Shader cache version bump

* Fix geometry shader recognition

* PR feedback

* Delete GetOperandDef and GetOperandUse

* Remove replacements that are no longer needed on GLSL compilation on Vulkan

* Fix incorrect load for per-patch outputs

* Fix build
2023-04-25 19:51:07 -03:00
gdkchan
097562bc6c Add missing check for thread termination on ArbitrateLock (#4722)
* Add missing check for thread termination on ArbitrateLock

* Use TerminationRequested in all places where it can be used
2023-04-25 19:33:14 -03:00
gdkchan
db4242c5dc Implement DMA texture copy component shuffle (#4717)
* Implement DMA texture copy component shuffle

* Set UInt24 alignment to 1
2023-04-24 15:28:03 +02:00
gdkchan
4dd77316f7 Use vector transform feedback outputs with fragment shaders (#4708)
* Use vector transform feedback outputs with fragment shaders

* Shader cache version bump

* Fix missing outputs when vector transform feedback outputs are used
2023-04-24 08:34:38 +02:00
TSRBerry
3f98369a17 Set the console title for GTK again (#4706)
Fixes a regression from #3707 where I accidentally removed that line.
2023-04-24 08:15:19 +02:00
TSRBerry
c26aeefe03 Fix amiibo timeout issues & log errors/exceptions (#4712) 2023-04-24 02:08:31 +00:00
jhorv
666e05f5cb Reducing Memory Allocations 202303 (#4624)
* use ArrayPool, avoid 6000-7000 allocs/sec of runtime

* use ArrayPool, avoid ~7k allocs/second during game execution

* use ArrayPool, avoid ~3000 allocs/sec during game execution

* use MemoryPool, reduce 0.5 MB/sec of new allocations during game execution

* avoid over-allocation by setting List<> Capacity when known

* remove LINQ in KTimeManager.UnscheduleFutureInvocation

* KTimeManager - avoid spinning one more time when the time has arrived

* KTimeManager - let SpinWait decide when to Thread.Yield(), and don't SpinOnce() immediately after Thread.Yield()

* use MemoryPool, reduce ~175k bytes/sec allocation during game execution

* IpcService - call commands via dynamic methods instead of reflection .Invoke(). Faster to call and with fewer allocations because parameters can be passed directly instead of as an array

* Make ButtonMappingEntry a record struct to avoid allocations. Set the List<ButtonMappingEntry> capacity according to use.

* add MemoryBuffer type for working with MemoryPool<byte>

* update changes to use MemoryBuffer

* make parameter ReadOnlySpan instead of Span

* whitespace fix

* Revert "IpcService - call commands via dynamic methods instead of reflection .Invoke(). Faster to call and with fewer allocations because parameters can be passed directly instead of as an array"

This reverts commit f2c698bdf65f049e8481c9f2ec7138d9b9a8261d.

* tweak KTimeManager spin behavior

* replace MemoryBuffer with ByteMemoryPool modeled after System.Buffers.ArrayMemoryPool<T>

* make ByteMemoryPoolBuffer responsible for renting memory
2023-04-24 02:06:23 +00:00
riperiperi
8d9d508dc7 Shader: Bias textureGather instructions on AMD/Intel (#4703)
* Experimental (GLSL, forced)

* SPIR-V attempt

* Add capability

* Fix pCount == 1 on glsl

* Fix typo
2023-04-22 18:02:39 -03:00
SpicerXD
e27f5522e2 Removed MotionInput Calibration (#4705)
Don't know why this is here.
It just seems to set the filter to an identity. Which then quickly returns to where its supposed to be anyways.
2023-04-22 15:31:28 +02:00
gdkchan
add2a9d151 Avoid LM service crashes by not reading more than the buffer size (#4701) 2023-04-20 17:10:17 +02:00
dependabot[bot]
9e50dd99d7 nuget: bump System.IdentityModel.Tokens.Jwt from 6.28.1 to 6.29.0 (#4694)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.28.1 to 6.29.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/6.28.1...6.29.0)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-17 21:31:12 +02:00
dependabot[bot]
0dec91bb42 nuget: bump System.Management from 7.0.0 to 7.0.1 (#4695)
Bumps [System.Management](https://github.com/dotnet/runtime) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v7.0.0...v7.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-17 21:30:40 +02:00
gdkchan
d9b63353b0 Support copy between multisample and non-multisample depth textures (#4676)
* Support copy between multisample and non-multisample depth textures

* PR feedback
2023-04-17 08:13:53 +00:00
gdkchan
eabd0ec93f Revert "chore: Update Silk.NET to 2.17.1 (#4686)" (#4690)
This reverts commit 79d1c190db.
2023-04-16 20:56:27 -03:00
riperiperi
138d5dc64a Vulkan: HashTableSlim lookup optimization (#4688) 2023-04-16 14:57:01 -03:00
gdkchan
3e68a87d63 Change SMAA filter texture clear method (#4685)
* Change SMAA filter texture clear method

* Alpha should be 1

* Delete more unnecessary code
2023-04-16 14:26:22 -03:00
TSRBerry
69b6ef7a4a [GUI] Add network interface dropdown (#4597)
* Add network adapter dropdown from LDN build

* Ava: Add NetworkInterfaces to SettingsNetworkTab

* Add headless network interface option

* Add network interface dropdown to Avalonia

* Fix handling network interfaces without a gateway address

* gtk: Actually save selected network interface to config

* Increment config version
2023-04-16 15:25:20 +00:00
Mary
40e87c634e Fix a crash in Ryujinx.Headless.SDL2 when loading an app (#4687)
Caused by the recent application loader changes.
2023-04-16 16:50:30 +02:00
Mary
79d1c190db chore: Update Silk.NET to 2.17.1 (#4686) 2023-04-16 09:38:07 +00:00
Ac_K
2bc88467eb Update README.md 2023-04-16 09:37:31 +00:00
Vincenzo Nizza
baf8752e74 Ensure the updater doesn't delete hidden or system files (#4626)
* Copy desktop.ini to update directory if it exists in HomeDir

* EnumerateFilesToDelete() exclude files with "Hidden" and "System" attributes
2023-04-16 09:19:33 +00:00
dependabot[bot]
d5e4378aea nuget: bump DynamicData from 7.13.1 to 7.13.5 (#4654)
Bumps [DynamicData](https://github.com/reactiveui/DynamicData) from 7.13.1 to 7.13.5.
- [Release notes](https://github.com/reactiveui/DynamicData/releases)
- [Changelog](https://github.com/reactivemarbles/DynamicData/blob/main/ReleaseNotes.md)
- [Commits](https://github.com/reactiveui/DynamicData/compare/7.13.1...7.13.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-16 09:02:06 +00:00
TSRBerry
6dbcdfea47 Ava: Fix nca extraction window never closing & minor cleanup (#4569)
* ava: Remove unused doWhileDeferred parameters

* ava: Minimally improve swkbd dialog

It's currently impossible to get the dialog to redirect focus to the InputBox.

* ava: Fix nca extraction dialog never closing

Also contains some minor cleanup
2023-04-16 07:09:02 +00:00
NitroTears
c5258cf082 Ability to hide file types in Game List (#4555)
* Added HiddenFileTypes to config state, and check to file enumeration

* Added hiddenfiletypes checkboxes to the UI

* Added Ava version of HiddenFileTypes

* Inverted Hide to Show with file types, minor formatting

* all variables with a reference to 'hidden' is now 'shown'

* one more variable name changed

* review feedback

* added FileTypes extension methof to get the correlating config value

* moved extension method to new folder and file in Ryujinx.Ui.Common

* added default case for ToggleFileType

* changed exception type to OutOfRangeException
2023-04-16 01:03:35 +00:00
Daniel Shala
5c89e22bb9 Added check for eventual symlink when displaying game files. (#4526)
* Added check for eventual symlink when displaying game files.

* Moved symlink check logic

* Moved symlink check logic

* Fixed prev commit

---------

Co-authored-by: Daniel Shala <danielshala00@gmail.com>
2023-04-15 16:11:24 +00:00
Alex Barney
11ecff2ff0 Rename Hipc to Cmif where appropriate (#3880) 2023-04-14 20:00:34 -03:00
MutantAura
4c3f09644a Move swkbd message null check into constructor (#4671) 2023-04-12 21:18:40 +02:00
TSRBerry
e187a8870a HLE: Deal with empty title names properly (#4643)
* hle: Deal with empty titleNames in some languages

* gui: Fix displaying the wrong title name

* Remove unnecessary bounds check

* Fix a NRE when getting the version string

* Restore empty string logic
2023-04-12 01:09:47 +00:00
riperiperi
a64fee29dc Vulkan: add situational "Fast Flush" mode (#4667)
* Flush in the middle of long command buffers.

* Vulkan: add situational "Fast Flush" mode

The AutoFlushCounter class was added to periodically flush Vulkan command buffers throughout a frame, which reduces latency to the GPU as commands are submitted and processed much sooner. This was done by allowing command buffers to flush when framebuffer attachments changed.

However, some games have incredibly long render passes with a large number of draws, and really aggressive data access that forces GPU sync.

The Vulkan backend could potentially end up building a single command buffer for 4-5ms if a pass has enough draws, such as in BOTW. In the scenario where sync is waited on immediately after submission, this would have to wait for the completion of a much longer command buffer than usual.

The solution is to force command buffer submission periodically in a "fast flush" mode. This will end up splitting render passes, but it will only enable if sync is aggressive enough.

This should improve performance in GPU limited scenarios, or in games that aggressively wait on synchronization. In some games, it may only kick in when res scaling. It won't trigger in games like SMO where sync is not an issue.

Improves performance in Pokemon Scarlet/Violet (res scaled) and BOTW (in general).

* Add conversions in milliseconds next to flush timers.
2023-04-11 09:23:41 +02:00
riperiperi
9ef94c8292 ARMeilleure: Move TPIDR_EL0 and TPIDRRO_EL0 to NativeContext (#4661)
* ARMeilleure: Move TPIDR_EL0 and TPIDRRO_EL0 to NativeContext

Some games access these system registers several tens of thousands of times in a second from many different threads. While this isn't really crippling, it is a lot of wasted time spent in a reverse pinvoke transition.

Example games are Pokemon Scarlet/Violet and BOTW. These games have a lot of different potential bottlenecks so it's unlikely you will see a consistent improvement, but it definitely disappears from the cpu profile.

* Remove unreachable code.

* Add ulong conversion for offsets

* Nit
2023-04-11 08:55:04 +02:00
riperiperi
915d6d044c OpenGL: Fix OBS/Overlays again by binding FB before present (#4668)
This seems to have been removed by the Post-Processing PR, but it is required for the display in OBS to be the right way up and properly scaled.

I've tested this with AA and FSR on MK8D and it seems to behave properly. Testing is welcome.
2023-04-11 08:32:31 +02:00
MutantAura
a4780ab33b Force activate parent window before dialog is shown (#4663) 2023-04-11 00:04:31 +02:00
TSRBerry
a947a45d81 gtk: Fix a NRE when disposing OpenGL (#4648) 2023-04-10 17:00:23 +02:00
riperiperi
9db73f74cf ARMeilleure: Respect FZ/RM flags for all floating point operations (#4618)
* ARMeilleure: Respect Fz flag for all floating point operations.

This is a change in strategy for emulating the Fz FPCR flag. Before, it was set before instructions that "needed it" and reset after. However, this missed a few hot instructions like the multiplication instruction, and the entirety of A32.

The new strategy is to set the Fz flag only in the following circumstances:

- Set to match FPCR before translated functions/loop are executed.
- Reset when calling SoftFloat methods, set when returning.
- Reset when exiting execution.

This allows us to remove the code around the existing Fz aware instructions, and get the accuracy benefits on all floating point instructions executed while in translated code.

Single step executions now need to be called with a context wrapper - right now it just contains the Fz flag initialization, and won't actually do anything on ARM.

This fixes a bug in Breath of the Wild where some physics interactions could randomly crash the game due to subnormal values not flushing to zero.

This is draft right now because I need to answer the questions:
- Does dotnet avoid changing the value of Mxcsr?
- Is it a good idea to assume that? Or should the flag set/restore be done on every managed method call, not just softfloat?
- If we assume that, do we want a unit test to verify the behaviour?

I recommend testing a bunch of games, especially games affected when this was originally added, such as #1611.

* Remove unused method

* Use FMA for Fmadd, Fmsub, Fnmadd, Fnmsub, Fmla, Fmls

...when available.

Similar implementation to A32

* Use FMA for Frecps, Frsqrts

* Don't set DAZ.

* Add round mode to ARM FP mode

* Fix mistakes

* Add test for FP state when calling managed methods

* Add explanatory comment to test.

* Cleanup

* Add A64 FPCR flags

* Vrintx_S A32 fast path on A64 backend

* Address feedback 1, re-enable DAZ

* Fix FMA instructions By Elem

* Address feedback
2023-04-10 12:22:58 +02:00
gdkchan
a1efd87c45 Implement remaining Arm64 HINT instructions as NOP (#4658)
* Implement remaining HINT instructions as NOP

* Split HINT encodings more to account for CSDB
2023-04-09 13:21:16 -03:00
jhorv
49be977588 Eliminate boxing allocations caused by ISampledData structs (#4556)
* Redesign use of ISampledData for accessing the SamplingNumber value on input data structs.

* Always read SamplingNumber as little-endian

* Restored field order for SixAxisSensorState. Rework to allow possibility of non-zero offsets for the SamplingNumber field. Set StructLayout Pack=8 - the KeyboardState struct is 4 bytes shorter with any other value.

* fix spelling

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

* set Pack = 1 for ISampledDataStruct types, added Unknown field to KeyboardState

* extend size of KeyboardModifier

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2023-04-05 17:42:32 -03:00
Mary
c95be55091 vulkan: Cleanup PhysicalDevice and Instance querying (#4632)
* vulkan: Move most of the properties enumeration to VulkanPhysicalDevice

That clean up a bit of duplicate logic.
Also move to use an hashset for device extensions.

* vulkan: Move instance querying to VulkanInstance

Also cleanup code to use span when possible instead of unsafe pointers.

* Address gdkchan's comments
2023-04-05 14:48:38 -03:00
325 changed files with 7022 additions and 4183 deletions

View File

@@ -226,6 +226,8 @@ namespace ARMeilleure.CodeGen.Arm64
Add(Intrinsic.Arm64MlsVe, new IntrinsicInfo(0x2f004000u, IntrinsicType.VectorTernaryRdByElem));
Add(Intrinsic.Arm64MlsV, new IntrinsicInfo(0x2e209400u, IntrinsicType.VectorTernaryRd));
Add(Intrinsic.Arm64MoviV, new IntrinsicInfo(0x0f000400u, IntrinsicType.VectorMovi));
Add(Intrinsic.Arm64MrsFpcr, new IntrinsicInfo(0xd53b4400u, IntrinsicType.GetRegister));
Add(Intrinsic.Arm64MsrFpcr, new IntrinsicInfo(0xd51b4400u, IntrinsicType.SetRegister));
Add(Intrinsic.Arm64MrsFpsr, new IntrinsicInfo(0xd53b4420u, IntrinsicType.GetRegister));
Add(Intrinsic.Arm64MsrFpsr, new IntrinsicInfo(0xd51b4420u, IntrinsicType.SetRegister));
Add(Intrinsic.Arm64MulVe, new IntrinsicInfo(0x0f008000u, IntrinsicType.VectorBinaryByElem));

View File

@@ -268,11 +268,13 @@ namespace ARMeilleure.CodeGen.X86
Add(X86Instruction.Vblendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4a, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vcvtph2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3813, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vcvtps2ph, new InstructionInfo(0x000f3a1d, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vfmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vfmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vfmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vfnmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfnmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66));
Add(X86Instruction.Vfnmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
Add(X86Instruction.Vfnmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66));

View File

@@ -249,10 +249,9 @@ namespace ARMeilleure.CodeGen.X86
case IntrinsicType.Mxcsr:
{
Operand offset = operation.GetSource(0);
Operand bits = operation.GetSource(1);
Debug.Assert(offset.Kind == OperandKind.Constant && bits.Kind == OperandKind.Constant);
Debug.Assert(offset.Type == OperandType.I32 && bits.Type == OperandType.I32);
Debug.Assert(offset.Kind == OperandKind.Constant);
Debug.Assert(offset.Type == OperandType.I32);
int offs = offset.AsInt32() + context.CallArgsRegionSize;
@@ -261,21 +260,23 @@ namespace ARMeilleure.CodeGen.X86
Debug.Assert(HardwareCapabilities.SupportsSse || HardwareCapabilities.SupportsVexEncoding);
context.Assembler.Stmxcsr(memOp);
if (operation.Intrinsic == Intrinsic.X86Mxcsrmb)
if (operation.Intrinsic == Intrinsic.X86Ldmxcsr)
{
context.Assembler.Or(memOp, bits, OperandType.I32);
Operand bits = operation.GetSource(1);
Debug.Assert(bits.Type == OperandType.I32);
context.Assembler.Mov(memOp, bits, OperandType.I32);
context.Assembler.Ldmxcsr(memOp);
}
else /* if (intrinOp.Intrinsic == Intrinsic.X86Mxcsrub) */
else if (operation.Intrinsic == Intrinsic.X86Stmxcsr)
{
Operand notBits = Const(~bits.AsInt32());
Operand dest = operation.Destination;
Debug.Assert(dest.Type == OperandType.I32);
context.Assembler.And(memOp, notBits, OperandType.I32);
context.Assembler.Stmxcsr(memOp);
context.Assembler.Mov(dest, memOp, OperandType.I32);
}
context.Assembler.Ldmxcsr(memOp);
break;
}

View File

@@ -60,6 +60,7 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Haddpd, new IntrinsicInfo(X86Instruction.Haddpd, IntrinsicType.Binary));
Add(Intrinsic.X86Haddps, new IntrinsicInfo(X86Instruction.Haddps, IntrinsicType.Binary));
Add(Intrinsic.X86Insertps, new IntrinsicInfo(X86Instruction.Insertps, IntrinsicType.TernaryImm));
Add(Intrinsic.X86Ldmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr));
Add(Intrinsic.X86Maxpd, new IntrinsicInfo(X86Instruction.Maxpd, IntrinsicType.Binary));
Add(Intrinsic.X86Maxps, new IntrinsicInfo(X86Instruction.Maxps, IntrinsicType.Binary));
Add(Intrinsic.X86Maxsd, new IntrinsicInfo(X86Instruction.Maxsd, IntrinsicType.Binary));
@@ -75,8 +76,6 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Mulps, new IntrinsicInfo(X86Instruction.Mulps, IntrinsicType.Binary));
Add(Intrinsic.X86Mulsd, new IntrinsicInfo(X86Instruction.Mulsd, IntrinsicType.Binary));
Add(Intrinsic.X86Mulss, new IntrinsicInfo(X86Instruction.Mulss, IntrinsicType.Binary));
Add(Intrinsic.X86Mxcsrmb, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); // Mask bits.
Add(Intrinsic.X86Mxcsrub, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); // Unmask bits.
Add(Intrinsic.X86Paddb, new IntrinsicInfo(X86Instruction.Paddb, IntrinsicType.Binary));
Add(Intrinsic.X86Paddd, new IntrinsicInfo(X86Instruction.Paddd, IntrinsicType.Binary));
Add(Intrinsic.X86Paddq, new IntrinsicInfo(X86Instruction.Paddq, IntrinsicType.Binary));
@@ -160,6 +159,7 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Sqrtps, new IntrinsicInfo(X86Instruction.Sqrtps, IntrinsicType.Unary));
Add(Intrinsic.X86Sqrtsd, new IntrinsicInfo(X86Instruction.Sqrtsd, IntrinsicType.Unary));
Add(Intrinsic.X86Sqrtss, new IntrinsicInfo(X86Instruction.Sqrtss, IntrinsicType.Unary));
Add(Intrinsic.X86Stmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr));
Add(Intrinsic.X86Subpd, new IntrinsicInfo(X86Instruction.Subpd, IntrinsicType.Binary));
Add(Intrinsic.X86Subps, new IntrinsicInfo(X86Instruction.Subps, IntrinsicType.Binary));
Add(Intrinsic.X86Subsd, new IntrinsicInfo(X86Instruction.Subsd, IntrinsicType.Binary));
@@ -170,11 +170,13 @@ namespace ARMeilleure.CodeGen.X86
Add(Intrinsic.X86Unpcklps, new IntrinsicInfo(X86Instruction.Unpcklps, IntrinsicType.Binary));
Add(Intrinsic.X86Vcvtph2ps, new IntrinsicInfo(X86Instruction.Vcvtph2ps, IntrinsicType.Unary));
Add(Intrinsic.X86Vcvtps2ph, new IntrinsicInfo(X86Instruction.Vcvtps2ph, IntrinsicType.BinaryImm));
Add(Intrinsic.X86Vfmadd231pd, new IntrinsicInfo(X86Instruction.Vfmadd231pd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmadd231ps, new IntrinsicInfo(X86Instruction.Vfmadd231ps, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmadd231sd, new IntrinsicInfo(X86Instruction.Vfmadd231sd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmadd231ss, new IntrinsicInfo(X86Instruction.Vfmadd231ss, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmsub231sd, new IntrinsicInfo(X86Instruction.Vfmsub231sd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfmsub231ss, new IntrinsicInfo(X86Instruction.Vfmsub231ss, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmadd231pd, new IntrinsicInfo(X86Instruction.Vfnmadd231pd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmadd231ps, new IntrinsicInfo(X86Instruction.Vfnmadd231ps, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmadd231sd, new IntrinsicInfo(X86Instruction.Vfnmadd231sd, IntrinsicType.Fma));
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));

View File

@@ -0,0 +1,15 @@
using System;
namespace ARMeilleure.CodeGen.X86
{
[Flags]
enum Mxcsr
{
Ftz = 1 << 15, // Flush To Zero.
Rhi = 1 << 14, // Round Mode high bit.
Rlo = 1 << 13, // Round Mode low bit.
Um = 1 << 11, // Underflow Mask.
Dm = 1 << 8, // Denormal Mask.
Daz = 1 << 6 // Denormals Are Zero.
}
}

View File

@@ -120,12 +120,18 @@ namespace ARMeilleure.CodeGen.X86
break;
case Instruction.Extended:
if (node.Intrinsic == Intrinsic.X86Mxcsrmb || node.Intrinsic == Intrinsic.X86Mxcsrub)
if (node.Intrinsic == Intrinsic.X86Ldmxcsr)
{
int stackOffset = stackAlloc.Allocate(OperandType.I32);
node.SetSources(new Operand[] { Const(stackOffset), node.GetSource(0) });
}
else if (node.Intrinsic == Intrinsic.X86Stmxcsr)
{
int stackOffset = stackAlloc.Allocate(OperandType.I32);
node.SetSources(new Operand[] { Const(stackOffset) });
}
break;
}
}

View File

@@ -208,11 +208,13 @@ namespace ARMeilleure.CodeGen.X86
Vblendvps,
Vcvtph2ps,
Vcvtps2ph,
Vfmadd231pd,
Vfmadd231ps,
Vfmadd231sd,
Vfmadd231ss,
Vfmsub231sd,
Vfmsub231ss,
Vfnmadd231pd,
Vfnmadd231ps,
Vfnmadd231sd,
Vfnmadd231ss,

View File

@@ -108,6 +108,13 @@ namespace ARMeilleure.Decoders
SetA64("11001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create);
SetA64("00010011100xxxxx0xxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create);
SetA64("10010011110xxxxxxxxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create);
SetA64("11010101000000110010000011011111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("11010101000000110010000011111111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("110101010000001100100001xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("1101010100000011001000100xx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("1101010100000011001000101>>11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("110101010000001100100011xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("11010101000000110010>>xxxxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint
SetA64("11010101000000110011xxxx11011111", InstName.Isb, InstEmit.Isb, OpCodeSystem.Create);
SetA64("xx001000110xxxxx1xxxxxxxxxxxxxxx", InstName.Ldar, InstEmit.Ldar, OpCodeMemEx.Create);
SetA64("1x001000011xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxp, InstEmit.Ldaxp, OpCodeMemEx.Create);

View File

@@ -615,14 +615,11 @@ namespace ARMeilleure.Instructions
{
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
{
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
Intrinsic addInst = (op.Size & 1) == 0 ? Intrinsic.X86Addps : Intrinsic.X86Addpd;
Intrinsic addInst = (op.Size & 1) == 0 ? Intrinsic.X86Addps : Intrinsic.X86Addpd;
return context.AddIntrinsic(addInst, op1, op2);
}, scalar: false, op1, op2);
return context.AddIntrinsic(addInst, op1, op2);
}, scalar: false, op1, op2);
});
}
@@ -696,17 +693,33 @@ namespace ARMeilleure.Instructions
Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm);
Operand res;
if (op.Size == 0)
{
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addss, a, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, a, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addss, a, res);
}
context.Copy(d, context.VectorZeroUpper96(res));
}
else /* if (op.Size == 1) */
{
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, a, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res);
}
context.Copy(d, context.VectorZeroUpper64(res));
}
@@ -730,10 +743,7 @@ namespace ARMeilleure.Instructions
{
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: true, op1, op2);
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: true);
}
else
@@ -755,10 +765,7 @@ namespace ARMeilleure.Instructions
{
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: false, op1, op2);
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: false);
}
else
@@ -886,10 +893,7 @@ namespace ARMeilleure.Instructions
{
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: false, op1, op2);
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: false, op1, op2);
});
}
@@ -914,10 +918,7 @@ namespace ARMeilleure.Instructions
{
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: false, op1, op2);
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true);
}, scalar: false, op1, op2);
});
}
@@ -940,10 +941,7 @@ namespace ARMeilleure.Instructions
{
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: true, op1, op2);
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: true);
}
else
@@ -965,10 +963,7 @@ namespace ARMeilleure.Instructions
{
EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: false, op1, op2);
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: false);
}
else
@@ -1096,10 +1091,7 @@ namespace ARMeilleure.Instructions
{
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: false, op1, op2);
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: false, op1, op2);
});
}
@@ -1124,10 +1116,7 @@ namespace ARMeilleure.Instructions
{
return EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: false, op1, op2);
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false);
}, scalar: false, op1, op2);
});
}
@@ -1146,6 +1135,37 @@ namespace ARMeilleure.Instructions
{
InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlaSe);
}
else if (Optimizations.UseFma)
{
OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp;
Operand d = GetVec(op.Rd);
Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm);
int sizeF = op.Size & 1;
if (sizeF == 0)
{
int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6;
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, d, n, res);
context.Copy(d, context.VectorZeroUpper96(res));
}
else /* if (sizeF == 1) */
{
int shuffleMask = op.Index | op.Index << 1;
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, d, n, res);
context.Copy(d, context.VectorZeroUpper64(res));
}
}
else
{
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
@@ -1171,11 +1191,19 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0)
{
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
}
if (op.RegisterSize == RegisterSize.Simd64)
{
@@ -1186,9 +1214,15 @@ namespace ARMeilleure.Instructions
}
else /* if (sizeF == 1) */
{
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
}
context.Copy(d, res);
}
@@ -1224,8 +1258,15 @@ namespace ARMeilleure.Instructions
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, res);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
res = context.AddIntrinsic(Intrinsic.X86Addps, d, res);
}
if (op.RegisterSize == RegisterSize.Simd64)
{
@@ -1240,8 +1281,15 @@ namespace ARMeilleure.Instructions
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, res);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res);
}
context.Copy(d, res);
}
@@ -1261,6 +1309,37 @@ namespace ARMeilleure.Instructions
{
InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlsSe);
}
else if (Optimizations.UseFma)
{
OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp;
Operand d = GetVec(op.Rd);
Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm);
int sizeF = op.Size & 1;
if (sizeF == 0)
{
int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6;
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, d, n, res);
context.Copy(d, context.VectorZeroUpper96(res));
}
else /* if (sizeF == 1) */
{
int shuffleMask = op.Index | op.Index << 1;
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, d, n, res);
context.Copy(d, context.VectorZeroUpper64(res));
}
}
else
{
EmitScalarTernaryOpByElemF(context, (op1, op2, op3) =>
@@ -1286,11 +1365,19 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0)
{
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
}
if (op.RegisterSize == RegisterSize.Simd64)
{
@@ -1301,9 +1388,15 @@ namespace ARMeilleure.Instructions
}
else /* if (sizeF == 1) */
{
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
}
context.Copy(d, res);
}
@@ -1339,8 +1432,15 @@ namespace ARMeilleure.Instructions
Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, res);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res);
res = context.AddIntrinsic(Intrinsic.X86Subps, d, res);
}
if (op.RegisterSize == RegisterSize.Simd64)
{
@@ -1355,8 +1455,15 @@ namespace ARMeilleure.Instructions
Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask));
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, res);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res);
res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res);
}
context.Copy(d, res);
}
@@ -1385,17 +1492,33 @@ namespace ARMeilleure.Instructions
Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm);
Operand res;
if (op.Size == 0)
{
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subss, a, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, a, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subss, a, res);
}
context.Copy(d, context.VectorZeroUpper96(res));
}
else /* if (op.Size == 1) */
{
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, a, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res);
}
context.Copy(d, context.VectorZeroUpper64(res));
}
@@ -1669,25 +1792,39 @@ namespace ARMeilleure.Instructions
Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm);
Operand res;
if (op.Size == 0)
{
Operand mask = X86GetScalar(context, -0f);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231ss, a, n, m);
}
else
{
Operand mask = X86GetScalar(context, -0f);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res);
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res);
}
context.Copy(d, context.VectorZeroUpper96(res));
}
else /* if (op.Size == 1) */
{
Operand mask = X86GetScalar(context, -0d);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231sd, a, n, m);
}
else
{
Operand mask = X86GetScalar(context, -0d);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res);
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res);
}
context.Copy(d, context.VectorZeroUpper64(res));
}
@@ -1716,25 +1853,39 @@ namespace ARMeilleure.Instructions
Operand n = GetVec(op.Rn);
Operand m = GetVec(op.Rm);
Operand res;
if (op.Size == 0)
{
Operand mask = X86GetScalar(context, -0f);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfmsub231ss, a, n, m);
}
else
{
Operand mask = X86GetScalar(context, -0f);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res);
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res);
}
context.Copy(d, context.VectorZeroUpper96(res));
}
else /* if (op.Size == 1) */
{
Operand mask = X86GetScalar(context, -0d);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfmsub231sd, a, n, m);
}
else
{
Operand mask = X86GetScalar(context, -0d);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res);
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res);
}
context.Copy(d, context.VectorZeroUpper64(res));
}
@@ -1830,13 +1981,22 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0)
{
Operand mask = X86GetScalar(context, 2f);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, mask, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subss, mask, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subss, mask, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF);
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
@@ -1845,9 +2005,16 @@ namespace ARMeilleure.Instructions
{
Operand mask = X86GetScalar(context, 2d);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, mask, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subsd, mask, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subsd, mask, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF);
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
@@ -1877,14 +2044,23 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0)
{
Operand mask = X86GetAllElements(context, 2f);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, mask, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subps, mask, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subps, mask, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
if (op.RegisterSize == RegisterSize.Simd64)
{
@@ -1897,10 +2073,17 @@ namespace ARMeilleure.Instructions
{
Operand mask = X86GetAllElements(context, 2d);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, mask, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subpd, mask, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subpd, mask, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF);
context.Copy(GetVec(op.Rd), res);
}
@@ -2113,20 +2296,32 @@ namespace ARMeilleure.Instructions
public static void Frintx_S(ArmEmitterContext context)
{
// TODO Arm64: Fast path. Should we set host FPCR?
EmitScalarUnaryOpF(context, (op1) =>
if (Optimizations.UseAdvSimd)
{
return EmitRoundByRMode(context, op1);
});
InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintxS);
}
else
{
EmitScalarUnaryOpF(context, (op1) =>
{
return EmitRoundByRMode(context, op1);
});
}
}
public static void Frintx_V(ArmEmitterContext context)
{
// TODO Arm64: Fast path. Should we set host FPCR?
EmitVectorUnaryOpF(context, (op1) =>
if (Optimizations.UseAdvSimd)
{
return EmitRoundByRMode(context, op1);
});
InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintxV);
}
else
{
EmitVectorUnaryOpF(context, (op1) =>
{
return EmitRoundByRMode(context, op1);
});
}
}
public static void Frintz_S(ArmEmitterContext context)
@@ -2237,16 +2432,25 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0)
{
Operand maskHalf = X86GetScalar(context, 0.5f);
Operand maskThree = X86GetScalar(context, 3f);
Operand maskOneHalf = X86GetScalar(context, 1.5f);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, maskThree, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subss, maskThree, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subss, maskThree, res);
res = context.AddIntrinsic(Intrinsic.X86Mulss, maskHalf, res);
res = context.AddIntrinsic(Intrinsic.X86Mulss, maskHalf, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF);
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
@@ -2257,10 +2461,17 @@ namespace ARMeilleure.Instructions
Operand maskThree = X86GetScalar(context, 3d);
Operand maskOneHalf = X86GetScalar(context, 1.5d);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, maskThree, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subsd, maskThree, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subsd, maskThree, res);
res = context.AddIntrinsic(Intrinsic.X86Mulsd, maskHalf, res);
res = context.AddIntrinsic(Intrinsic.X86Mulsd, maskHalf, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF);
context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res));
@@ -2290,15 +2501,24 @@ namespace ARMeilleure.Instructions
int sizeF = op.Size & 1;
Operand res;
if (sizeF == 0)
{
Operand maskHalf = X86GetAllElements(context, 0.5f);
Operand maskThree = X86GetAllElements(context, 3f);
Operand maskOneHalf = X86GetAllElements(context, 1.5f);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, maskThree, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res);
res = context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF);
@@ -2315,9 +2535,16 @@ namespace ARMeilleure.Instructions
Operand maskThree = X86GetAllElements(context, 3d);
Operand maskOneHalf = X86GetAllElements(context, 1.5d);
Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
if (Optimizations.UseFma)
{
res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, maskThree, n, m);
}
else
{
res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m);
res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res);
}
res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res);
res = context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res);
res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF);
@@ -4728,53 +4955,6 @@ namespace ARMeilleure.Instructions
}
}
public static Operand EmitSseOrAvxHandleFzModeOpF(
ArmEmitterContext context,
Func2I emit,
bool scalar,
Operand n = default,
Operand m = default)
{
Operand nCopy = n == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rn)) : n;
Operand mCopy = m == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rm)) : m;
EmitSseOrAvxEnterFtzAndDazModesOpF(context, out Operand isTrue);
Operand res = emit(nCopy, mCopy);
EmitSseOrAvxExitFtzAndDazModesOpF(context, isTrue);
if (n != default || m != default)
{
return res;
}
int sizeF = ((IOpCodeSimd)context.CurrOp).Size & 1;
if (sizeF == 0)
{
if (scalar)
{
res = context.VectorZeroUpper96(res);
}
else if (((OpCodeSimdReg)context.CurrOp).RegisterSize == RegisterSize.Simd64)
{
res = context.VectorZeroUpper64(res);
}
}
else /* if (sizeF == 1) */
{
if (scalar)
{
res = context.VectorZeroUpper64(res);
}
}
context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res);
return default;
}
private static Operand EmitSse2VectorMaxMinOpF(ArmEmitterContext context, Operand n, Operand m, bool isMax)
{
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
@@ -4834,10 +5014,7 @@ namespace ARMeilleure.Instructions
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
}, scalar: scalar, op1, op2);
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
}, scalar: scalar, nCopy, mCopy);
if (n != default || m != default)
@@ -4872,10 +5049,7 @@ namespace ARMeilleure.Instructions
Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) =>
{
return EmitSseOrAvxHandleFzModeOpF(context, (op1, op2) =>
{
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
}, scalar: scalar, op1, op2);
return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum);
}, scalar: scalar, nCopy, mCopy);
if (n != default || m != default)

View File

@@ -356,9 +356,11 @@ namespace ARMeilleure.Instructions
? typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert))
: typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert));
context.ExitArmFpMode();
context.StoreToContext();
Operand res = context.Call(method, src);
context.LoadFromContext();
context.EnterArmFpMode();
InsertScalar16(context, op.Vd, op.T, res);
}
@@ -372,9 +374,11 @@ namespace ARMeilleure.Instructions
? typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert))
: typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert));
context.ExitArmFpMode();
context.StoreToContext();
Operand res = context.Call(method, src);
context.LoadFromContext();
context.EnterArmFpMode();
InsertScalar(context, op.Vd, res);
}
@@ -542,10 +546,17 @@ namespace ARMeilleure.Instructions
// VRINTX (floating-point).
public static void Vrintx_S(ArmEmitterContext context)
{
EmitScalarUnaryOpF32(context, (op1) =>
if (Optimizations.UseAdvSimd)
{
return EmitRoundByRMode(context, op1);
});
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintxS);
}
else
{
EmitScalarUnaryOpF32(context, (op1) =>
{
return EmitRoundByRMode(context, op1);
});
}
}
private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, OperandType type, bool signed)

View File

@@ -1,3 +1,4 @@
using ARMeilleure.CodeGen.X86;
using ARMeilleure.Decoders;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State;
@@ -158,6 +159,75 @@ namespace ARMeilleure.Instructions
};
#endregion
public static void EnterArmFpMode(EmitterContext context, Func<FPState, Operand> getFpFlag)
{
if (Optimizations.UseSse2)
{
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
Operand fzTrue = getFpFlag(FPState.FzFlag);
Operand r0True = getFpFlag(FPState.RMode0Flag);
Operand r1True = getFpFlag(FPState.RMode1Flag);
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo)));
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(fzTrue, Const((int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Um | Mxcsr.Dm)), Const(0)));
// X86 round modes in order: nearest, negative, positive, zero
// ARM round modes in order: nearest, positive, negative, zero
// Read the bits backwards to correct this.
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r0True, Const((int)Mxcsr.Rhi), Const(0)));
mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r1True, Const((int)Mxcsr.Rlo), Const(0)));
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
}
else if (Optimizations.UseAdvSimd)
{
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
Operand fzTrue = getFpFlag(FPState.FzFlag);
Operand r0True = getFpFlag(FPState.RMode0Flag);
Operand r1True = getFpFlag(FPState.RMode1Flag);
fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1)));
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(fzTrue, Const((int)FPCR.Fz), Const(0)));
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r0True, Const((int)FPCR.RMode0), Const(0)));
fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r1True, Const((int)FPCR.RMode1), Const(0)));
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
// TODO: Restore FPSR
}
}
public static void ExitArmFpMode(EmitterContext context, Action<FPState, Operand> setFpFlag)
{
if (Optimizations.UseSse2)
{
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
// Unset round mode (to nearest) and ftz.
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo)));
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
// Status flags would be stored here if they were used.
}
else if (Optimizations.UseAdvSimd)
{
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
// Unset round mode (to nearest) and fz.
fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1)));
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
// TODO: Store FPSR
}
}
public static int GetImmShl(OpCodeSimdShImm op)
{
return op.Imm - (8 << op.Size);
@@ -465,9 +535,11 @@ namespace ARMeilleure.Instructions
? typeof(SoftFloat32).GetMethod(name)
: typeof(SoftFloat64).GetMethod(name);
context.ExitArmFpMode();
context.StoreToContext();
Operand res = context.Call(info, callArgs);
context.LoadFromContext();
context.EnterArmFpMode();
return res;
}
@@ -1358,39 +1430,6 @@ namespace ARMeilleure.Instructions
}
}
[Flags]
public enum Mxcsr
{
Ftz = 1 << 15, // Flush To Zero.
Um = 1 << 11, // Underflow Mask.
Dm = 1 << 8, // Denormal Mask.
Daz = 1 << 6 // Denormals Are Zero.
}
public static void EmitSseOrAvxEnterFtzAndDazModesOpF(ArmEmitterContext context, out Operand isTrue)
{
isTrue = GetFpFlag(FPState.FzFlag);
Operand lblTrue = Label();
context.BranchIfFalse(lblTrue, isTrue);
context.AddIntrinsicNoRet(Intrinsic.X86Mxcsrmb, Const((int)(Mxcsr.Ftz | Mxcsr.Um | Mxcsr.Dm | Mxcsr.Daz)));
context.MarkLabel(lblTrue);
}
public static void EmitSseOrAvxExitFtzAndDazModesOpF(ArmEmitterContext context, Operand isTrue = default)
{
isTrue = isTrue == default ? GetFpFlag(FPState.FzFlag) : isTrue;
Operand lblTrue = Label();
context.BranchIfFalse(lblTrue, isTrue);
context.AddIntrinsicNoRet(Intrinsic.X86Mxcsrub, Const((int)(Mxcsr.Ftz | Mxcsr.Daz)));
context.MarkLabel(lblTrue);
}
public enum CmpCondition
{
// Legacy Sse.

View File

@@ -1197,9 +1197,11 @@ namespace ARMeilleure.Instructions
Array.Resize(ref callArgs, callArgs.Length + 1);
callArgs[callArgs.Length - 1] = Const(1);
context.ExitArmFpMode();
context.StoreToContext();
Operand res = context.Call(info, callArgs);
context.LoadFromContext();
context.EnterArmFpMode();
return res;
}

View File

@@ -33,8 +33,8 @@ namespace ARMeilleure.Instructions
case 0b11_011_0100_0010_000: EmitGetNzcv(context); return;
case 0b11_011_0100_0100_000: EmitGetFpcr(context); return;
case 0b11_011_0100_0100_001: EmitGetFpsr(context); return;
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)); break;
case 0b11_011_1101_0000_011: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)); break;
case 0b11_011_1101_0000_010: EmitGetTpidrEl0(context); return;
case 0b11_011_1101_0000_011: EmitGetTpidrroEl0(context); return;
case 0b11_011_1110_0000_000: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0)); break;
case 0b11_011_1110_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break;
case 0b11_011_1110_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)); break;
@@ -49,19 +49,15 @@ namespace ARMeilleure.Instructions
{
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
MethodInfo info;
switch (GetPackedId(op))
{
case 0b11_011_0100_0010_000: EmitSetNzcv(context); return;
case 0b11_011_0100_0100_000: EmitSetFpcr(context); return;
case 0b11_011_0100_0100_001: EmitSetFpsr(context); return;
case 0b11_011_1101_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl0)); break;
case 0b11_011_1101_0000_010: EmitSetTpidrEl0(context); return;
default: throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
}
context.Call(info, GetIntOrZR(context, op.Rt));
}
public static void Nop(ArmEmitterContext context)
@@ -165,6 +161,28 @@ namespace ARMeilleure.Instructions
SetIntOrZR(context, op.Rt, fpsr);
}
private static void EmitGetTpidrEl0(ArmEmitterContext context)
{
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())));
SetIntOrZR(context, op.Rt, result);
}
private static void EmitGetTpidrroEl0(ArmEmitterContext context)
{
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset())));
SetIntOrZR(context, op.Rt, result);
}
private static void EmitSetNzcv(ArmEmitterContext context)
{
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
@@ -192,6 +210,8 @@ namespace ARMeilleure.Instructions
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpcr, Const(flag)), Const(1)));
}
}
context.UpdateArmFpMode();
}
private static void EmitSetFpsr(ArmEmitterContext context)
@@ -210,6 +230,19 @@ namespace ARMeilleure.Instructions
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpsr, Const(flag)), Const(1)));
}
}
context.UpdateArmFpMode();
}
private static void EmitSetTpidrEl0(ArmEmitterContext context)
{
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
Operand value = GetIntOrZR(context, op.Rt);
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), value);
}
}
}

View File

@@ -23,8 +23,6 @@ namespace ARMeilleure.Instructions
return;
}
MethodInfo info;
switch (op.CRn)
{
case 13: // Process and Thread Info.
@@ -36,14 +34,12 @@ namespace ARMeilleure.Instructions
switch (op.Opc2)
{
case 2:
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032)); break;
EmitSetTpidrEl0(context); return;
default:
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
}
break;
case 7:
switch (op.CRm) // Cache and Memory barrier.
{
@@ -64,8 +60,6 @@ namespace ARMeilleure.Instructions
default:
throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
}
context.Call(info, GetIntA32(context, op.Rt));
}
public static void Mrc(ArmEmitterContext context)
@@ -79,7 +73,7 @@ namespace ARMeilleure.Instructions
return;
}
MethodInfo info;
Operand result;
switch (op.CRn)
{
@@ -92,10 +86,10 @@ namespace ARMeilleure.Instructions
switch (op.Opc2)
{
case 2:
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032)); break;
result = EmitGetTpidrEl0(context); break;
case 3:
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32)); break;
result = EmitGetTpidrroEl0(context); break;
default:
throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X}).");
@@ -110,13 +104,13 @@ namespace ARMeilleure.Instructions
if (op.Rt == RegisterAlias.Aarch32Pc)
{
// Special behavior: copy NZCV flags into APSR.
EmitSetNzcv(context, context.Call(info));
EmitSetNzcv(context, result);
return;
}
else
{
SetIntA32(context, op.Rt, context.Call(info));
SetIntA32(context, op.Rt, result);
}
}
@@ -321,6 +315,37 @@ namespace ARMeilleure.Instructions
SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpscr, Const(flag)), Const(1)));
}
}
context.UpdateArmFpMode();
}
private static Operand EmitGetTpidrEl0(ArmEmitterContext context)
{
OpCode32System op = (OpCode32System)context.CurrOp;
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())));
}
private static Operand EmitGetTpidrroEl0(ArmEmitterContext context)
{
OpCode32System op = (OpCode32System)context.CurrOp;
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset())));
}
private static void EmitSetTpidrEl0(ArmEmitterContext context)
{
OpCode32System op = (OpCode32System)context.CurrOp;
Operand value = GetIntA32(context, op.Rt);
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), context.ZeroExtend32(OperandType.I64, value));
}
}
}

View File

@@ -72,26 +72,6 @@ namespace ARMeilleure.Instructions
return (ulong)GetContext().DczidEl0;
}
public static ulong GetTpidrEl0()
{
return (ulong)GetContext().TpidrEl0;
}
public static uint GetTpidrEl032()
{
return (uint)GetContext().TpidrEl0;
}
public static ulong GetTpidrroEl0()
{
return (ulong)GetContext().TpidrroEl0;
}
public static uint GetTpidr32()
{
return (uint)GetContext().TpidrroEl0;
}
public static ulong GetCntfrqEl0()
{
return GetContext().CntfrqEl0;
@@ -106,16 +86,6 @@ namespace ARMeilleure.Instructions
{
return GetContext().CntvctEl0;
}
public static void SetTpidrEl0(ulong value)
{
GetContext().TpidrEl0 = (long)value;
}
public static void SetTpidrEl032(uint value)
{
GetContext().TpidrEl0 = (long)value;
}
#endregion
#region "Read"

View File

@@ -53,6 +53,7 @@ namespace ARMeilleure.IntermediateRepresentation
X86Haddpd,
X86Haddps,
X86Insertps,
X86Ldmxcsr,
X86Maxpd,
X86Maxps,
X86Maxsd,
@@ -68,8 +69,6 @@ namespace ARMeilleure.IntermediateRepresentation
X86Mulps,
X86Mulsd,
X86Mulss,
X86Mxcsrmb,
X86Mxcsrub,
X86Paddb,
X86Paddd,
X86Paddq,
@@ -153,6 +152,7 @@ namespace ARMeilleure.IntermediateRepresentation
X86Sqrtps,
X86Sqrtsd,
X86Sqrtss,
X86Stmxcsr,
X86Subpd,
X86Subps,
X86Subsd,
@@ -163,11 +163,13 @@ namespace ARMeilleure.IntermediateRepresentation
X86Unpcklps,
X86Vcvtph2ps,
X86Vcvtps2ph,
X86Vfmadd231pd,
X86Vfmadd231ps,
X86Vfmadd231sd,
X86Vfmadd231ss,
X86Vfmsub231sd,
X86Vfmsub231ss,
X86Vfnmadd231pd,
X86Vfnmadd231ps,
X86Vfnmadd231sd,
X86Vfnmadd231ss,
@@ -394,6 +396,8 @@ namespace ARMeilleure.IntermediateRepresentation
Arm64MlsVe,
Arm64MlsV,
Arm64MoviV,
Arm64MrsFpcr,
Arm64MsrFpcr,
Arm64MrsFpsr,
Arm64MsrFpsr,
Arm64MulVe,

View File

@@ -27,8 +27,17 @@ namespace ARMeilleure.State
// Since EL2 isn't implemented, CNTVOFF_EL2 = 0
public ulong CntvctEl0 => CntpctEl0;
public long TpidrEl0 { get; set; }
public long TpidrroEl0 { get; set; }
public long TpidrEl0
{
get => _nativeContext.GetTpidrEl0();
set => _nativeContext.SetTpidrEl0(value);
}
public long TpidrroEl0
{
get => _nativeContext.GetTpidrroEl0();
set => _nativeContext.SetTpidrroEl0(value);
}
public uint Pstate
{

View File

@@ -13,6 +13,8 @@ namespace ARMeilleure.State
public fixed ulong V[RegisterConsts.VecRegsCount * 2];
public fixed uint Flags[RegisterConsts.FlagsCount];
public fixed uint FpFlags[RegisterConsts.FpFlagsCount];
public long TpidrEl0;
public long TpidrroEl0;
public int Counter;
public ulong DispatchAddress;
public ulong ExclusiveAddress;
@@ -168,6 +170,12 @@ namespace ARMeilleure.State
}
}
public long GetTpidrEl0() => GetStorage().TpidrEl0;
public void SetTpidrEl0(long value) => GetStorage().TpidrEl0 = value;
public long GetTpidrroEl0() => GetStorage().TpidrroEl0;
public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value;
public int GetCounter() => GetStorage().Counter;
public void SetCounter(int value) => GetStorage().Counter = value;
@@ -214,6 +222,16 @@ namespace ARMeilleure.State
}
}
public static int GetTpidrEl0Offset()
{
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrEl0);
}
public static int GetTpidrroEl0Offset()
{
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0);
}
public static int GetCounterOffset()
{
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);

View File

@@ -188,6 +188,21 @@ namespace ARMeilleure.Translation
}
}
public void EnterArmFpMode()
{
InstEmitSimdHelper.EnterArmFpMode(this, InstEmitHelper.GetFpFlag);
}
public void UpdateArmFpMode()
{
EnterArmFpMode();
}
public void ExitArmFpMode()
{
InstEmitSimdHelper.ExitArmFpMode(this, (flag, value) => InstEmitHelper.SetFpFlag(this, flag, value));
}
public Operand TryGetComparisonResult(Condition condition)
{
if (_optOpLastCompare == null || _optOpLastCompare != _optOpLastFlagSet)

View File

@@ -105,17 +105,11 @@ namespace ARMeilleure.Translation
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrroEl0)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only.
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032))); // A32 only.
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl0)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032))); // A32 only.
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SupervisorCall)));
SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)));

View File

@@ -3,4 +3,5 @@
namespace ARMeilleure.Translation
{
delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress);
delegate ulong WrapperFunction(IntPtr nativeContext, ulong startAddress);
}

View File

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

View File

@@ -25,5 +25,10 @@ namespace ARMeilleure.Translation
{
return _func(context.NativeContextPtr);
}
public ulong Execute(WrapperFunction dispatcher, State.ExecutionContext context)
{
return dispatcher(context.NativeContextPtr, (ulong)FuncPointer);
}
}
}

View File

@@ -183,7 +183,7 @@ namespace ARMeilleure.Translation
Statistics.StartTimer();
ulong nextAddr = func.Execute(context);
ulong nextAddr = func.Execute(Stubs.ContextWrapper, context);
Statistics.StopTimer(address);
@@ -194,7 +194,7 @@ namespace ARMeilleure.Translation
{
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
address = func.Execute(context);
address = func.Execute(Stubs.ContextWrapper, context);
EnqueueForDeletion(address, func);

View File

@@ -21,6 +21,7 @@ namespace ARMeilleure.Translation
private readonly Translator _translator;
private readonly Lazy<IntPtr> _dispatchStub;
private readonly Lazy<DispatcherFunction> _dispatchLoop;
private readonly Lazy<WrapperFunction> _contextWrapper;
/// <summary>
/// Gets the dispatch stub.
@@ -64,6 +65,20 @@ namespace ARMeilleure.Translation
}
}
/// <summary>
/// Gets the context wrapper function.
/// </summary>
/// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
public WrapperFunction ContextWrapper
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _contextWrapper.Value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
/// <see cref="Translator"/> instance.
@@ -77,6 +92,7 @@ namespace ARMeilleure.Translation
_translator = translator;
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
_contextWrapper = new(GenerateContextWrapper, isThreadSafe: true);
}
/// <summary>
@@ -202,6 +218,32 @@ namespace ARMeilleure.Translation
return Marshal.GetFunctionPointerForDelegate(func);
}
/// <summary>
/// Emits code that syncs FP state before executing guest code, or returns it to normal.
/// </summary>
/// <param name="context">Emitter context for the method</param>
/// <param name="nativeContext">Pointer to the native context</param>
/// <param name="enter">True if entering guest code, false otherwise</param>
private void EmitSyncFpContext(EmitterContext context, Operand nativeContext, bool enter)
{
if (enter)
{
InstEmitSimdHelper.EnterArmFpMode(context, (flag) =>
{
Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
return context.Load(OperandType.I32, flagAddress);
});
}
else
{
InstEmitSimdHelper.ExitArmFpMode(context, (flag, value) =>
{
Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
context.Store(flagAddress, value);
});
}
}
/// <summary>
/// Generates a <see cref="DispatchLoop"/> function.
/// </summary>
@@ -221,6 +263,8 @@ namespace ARMeilleure.Translation
Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
EmitSyncFpContext(context, nativeContext, true);
context.MarkLabel(beginLbl);
context.Store(dispatchAddress, guestAddress);
context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
@@ -229,6 +273,9 @@ namespace ARMeilleure.Translation
context.Branch(beginLbl);
context.MarkLabel(endLbl);
EmitSyncFpContext(context, nativeContext, false);
context.Return();
var cfg = context.GetControlFlowGraph();
@@ -237,5 +284,29 @@ namespace ARMeilleure.Translation
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>();
}
/// <summary>
/// Generates a <see cref="ContextWrapper"/> function.
/// </summary>
/// <returns><see cref="ContextWrapper"/> function</returns>
private WrapperFunction GenerateContextWrapper()
{
var context = new EmitterContext();
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
Operand guestMethod = context.LoadArgument(OperandType.I64, 1);
EmitSyncFpContext(context, nativeContext, true);
Operand returnValue = context.Call(guestMethod, OperandType.I64, nativeContext);
EmitSyncFpContext(context, nativeContext, false);
context.Return(returnValue);
var cfg = context.GetControlFlowGraph();
var retType = OperandType.I64;
var argTypes = new[] { OperandType.I64, OperandType.I64 };
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>();
}
}
}

View File

@@ -0,0 +1,148 @@
using ARMeilleure.CodeGen.X86;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State;
using ARMeilleure.Translation;
using System;
using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
namespace ARMeilleure.Translation
{
public static class TranslatorTestMethods
{
public delegate int FpFlagsPInvokeTest(IntPtr managedMethod);
private static bool SetPlatformFtz(EmitterContext context, bool ftz)
{
if (Optimizations.UseSse2)
{
Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr);
if (ftz)
{
mxcsr = context.BitwiseOr(mxcsr, Const((int)(Mxcsr.Ftz | Mxcsr.Um | Mxcsr.Dm)));
}
else
{
mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)Mxcsr.Ftz));
}
context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr);
return true;
}
else if (Optimizations.UseAdvSimd)
{
Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr);
if (ftz)
{
fpcr = context.BitwiseOr(fpcr, Const((int)FPCR.Fz));
}
else
{
fpcr = context.BitwiseAnd(fpcr, Const(~(int)FPCR.Fz));
}
context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr);
return true;
}
else
{
return false;
}
}
private static Operand FpBitsToInt(EmitterContext context, Operand fp)
{
Operand vec = context.VectorInsert(context.VectorZero(), fp, 0);
return context.VectorExtract(OperandType.I32, vec, 0);
}
public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest()
{
EmitterContext context = new EmitterContext();
Operand methodAddress = context.Copy(context.LoadArgument(OperandType.I64, 0));
// Verify that default dotnet fp state does not flush to zero.
// This is required for SoftFloat to function.
// Denormal + zero != 0
Operand denormal = ConstF(BitConverter.Int32BitsToSingle(1)); // 1.40129846432e-45
Operand zeroF = ConstF(0f);
Operand zero = Const(0);
Operand result = context.Add(zeroF, denormal);
// Must not be zero.
Operand correct1Label = Label();
context.BranchIfFalse(correct1Label, context.ICompareEqual(FpBitsToInt(context, result), zero));
context.Return(Const(1));
context.MarkLabel(correct1Label);
// Set flush to zero flag. If unsupported by the backend, just return true.
if (!SetPlatformFtz(context, true))
{
context.Return(Const(0));
}
// Denormal + zero == 0
Operand resultFz = context.Add(zeroF, denormal);
// Must equal zero.
Operand correct2Label = Label();
context.BranchIfTrue(correct2Label, context.ICompareEqual(FpBitsToInt(context, resultFz), zero));
SetPlatformFtz(context, false);
context.Return(Const(2));
context.MarkLabel(correct2Label);
// Call a managed method. This method should not change Fz state.
context.Call(methodAddress, OperandType.None);
// Denormal + zero == 0
Operand resultFz2 = context.Add(zeroF, denormal);
// Must equal zero.
Operand correct3Label = Label();
context.BranchIfTrue(correct3Label, context.ICompareEqual(FpBitsToInt(context, resultFz2), zero));
SetPlatformFtz(context, false);
context.Return(Const(3));
context.MarkLabel(correct3Label);
// Success.
SetPlatformFtz(context, false);
context.Return(Const(0));
// Compile and return the function.
ControlFlowGraph cfg = context.GetControlFlowGraph();
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>();
}
}
}

View File

@@ -13,7 +13,7 @@
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
<PackageVersion Include="DynamicData" Version="7.13.1" />
<PackageVersion Include="DynamicData" Version="7.13.5" />
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
@@ -44,9 +44,9 @@
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.28.1" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.29.0" />
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
<PackageVersion Include="System.Management" Version="7.0.0" />
<PackageVersion Include="System.Management" Version="7.0.1" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.6.1" />
</ItemGroup>

View File

@@ -40,7 +40,7 @@
## Compatibility
As of November 2022, Ryujinx has been tested on approximately 3,800 titles; over 3,600 boot past menus and into gameplay, with roughly 3,200 of those being considered playable.
As of April 2023, Ryujinx has been tested on approximately 4,050 titles; over 4,000 boot past menus and into gameplay, with roughly 3,400 of those being considered playable.
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
## Usage

View File

@@ -11,6 +11,7 @@ using Ryujinx.Audio.Renderer.Server.Voice;
using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common.Logging;
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -149,12 +150,16 @@ namespace Ryujinx.Audio.Renderer.Server
state.InUse = false;
}
Memory<VoiceUpdateState>[] voiceUpdateStatesArray = ArrayPool<Memory<VoiceUpdateState>>.Shared.Rent(Constants.VoiceChannelCountMax);
Span<Memory<VoiceUpdateState>> voiceUpdateStates = voiceUpdateStatesArray.AsSpan(0, Constants.VoiceChannelCountMax);
// Start processing
for (int i = 0; i < context.GetCount(); i++)
{
VoiceInParameter parameter = parameters[i];
Memory<VoiceUpdateState>[] voiceUpdateStates = new Memory<VoiceUpdateState>[Constants.VoiceChannelCountMax];
voiceUpdateStates.Fill(Memory<VoiceUpdateState>.Empty);
ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<VoiceOutStatus>(ref _output)[0];
@@ -197,6 +202,8 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
ArrayPool<Memory<VoiceUpdateState>>.Shared.Return(voiceUpdateStatesArray);
int currentOutputSize = _output.Length;
OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf<VoiceOutStatus>() * context.GetCount());

View File

@@ -378,7 +378,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
/// <param name="outStatus">The given user output.</param>
/// <param name="parameter">The user parameter.</param>
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, Memory<VoiceUpdateState>[] voiceUpdateStates)
public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates)
{
#if DEBUG
// Sanity check in debug mode of the internal state
@@ -424,7 +424,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice
/// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param>
/// <param name="mapper">The mapper to use.</param>
/// <param name="behaviourContext">The behaviour context.</param>
public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, Memory<VoiceUpdateState>[] voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext)
{
errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2];

View File

@@ -177,6 +177,8 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
_gpuCancellationTokenSource = new CancellationTokenSource();
}
@@ -321,17 +323,15 @@ namespace Ryujinx.Ava
_viewModel.IsGameRunning = true;
var activeProcess = Device.Processes.ActiveApplication;
var nacp = activeProcess.ApplicationControlProperties;
int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}";
string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $" v{activeProcess.DisplayVersion}";
string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})";
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
Dispatcher.UIThread.InvokeAsync(() =>
{
_viewModel.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
_viewModel.Title = $"Ryujinx {Program.Version} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
});
_viewModel.SetUIProgressHandlers(Device);
@@ -385,6 +385,11 @@ namespace Ryujinx.Ava
});
}
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
{
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
}
public void Stop()
{
_isActive = false;
@@ -741,7 +746,8 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.System.IgnoreMissingServices,
ConfigurationState.Instance.Graphics.AspectRatio,
ConfigurationState.Instance.System.AudioVolume,
ConfigurationState.Instance.System.UseHypervisor);
ConfigurationState.Instance.System.UseHypervisor,
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
Device = new Switch(configuration);
}

View File

@@ -429,6 +429,7 @@
"DlcManagerEnableAllButton": "Enable All",
"DlcManagerDisableAllButton": "Disable All",
"MenuBarOptionsChangeLanguage": "Change Language",
"MenuBarShowFileTypes": "Show File Types",
"CommonSort": "Sort",
"CommonShowNames": "Show Names",
"CommonFavorite": "Favorite",
@@ -637,5 +638,8 @@
"SmaaHigh": "SMAA High",
"SmaaUltra": "SMAA Ultra",
"UserEditorTitle" : "Edit User",
"UserEditorTitleCreate" : "Create User"
"UserEditorTitleCreate" : "Create User",
"SettingsTabNetworkInterface": "Network Interface:",
"NetworkInterfaceTooltip": "The network interface used for LAN features",
"NetworkInterfaceDefault": "Default"
}

View File

@@ -13,6 +13,7 @@ using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Logging;
@@ -152,25 +153,17 @@ namespace Ryujinx.Ava.Common
string destination = await folderDialog.ShowAsync(_owner);
var cancellationToken = new CancellationTokenSource();
UpdateWaitWindow waitingDialog = new(
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
cancellationToken);
if (!string.IsNullOrWhiteSpace(destination))
{
Thread extractorThread = new(() =>
{
Dispatcher.UIThread.Post(async () =>
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
"",
"",
LocaleManager.Instance[LocaleKeys.InputDialogCancel],
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle]);
Dispatcher.UIThread.Post(waitingDialog.Show);
if (result == UserResult.Cancel)
{
cancellationToken.Cancel();
}
});
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
Nca mainNca = null;
@@ -222,6 +215,8 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
});
@@ -263,11 +258,15 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
});
}
else if (resultCode.Value.IsSuccess())
{
Dispatcher.UIThread.Post(waitingDialog.Close);
NotificationHelper.Show(
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
@@ -284,6 +283,8 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () =>
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(ex.Message);
});
}

View File

@@ -730,7 +730,7 @@ namespace Ryujinx.Modules
}
}
return files;
return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System));
}
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog)

View File

@@ -53,6 +53,8 @@ namespace Ryujinx.Ava.UI.Applet
bool opened = false;
_parent.Activate();
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
title,
message,

View File

@@ -48,6 +48,7 @@
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Focusable="True"
KeyUp="Message_KeyUp"
Text="{Binding Message}"
TextInput="Message_TextInput"
@@ -61,4 +62,4 @@
HorizontalAlignment="Stretch"
TextWrapping="Wrap" />
</Grid>
</UserControl>
</UserControl>

View File

@@ -23,10 +23,11 @@ namespace Ryujinx.Ava.UI.Controls
private ContentDialog _host;
public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder)
public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder, string message)
{
MainText = mainText;
SecondaryText = secondaryText;
Message = message ?? "";
DataContext = this;
_placeholder = placeholder;
InitializeComponent();
@@ -44,6 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
InitializeComponent();
}
protected override void OnGotFocus(GotFocusEventArgs e)
{
// FIXME: This does not work. Might be a bug in Avalonia with DialogHost
// Currently focus will be redirected to the overlay window instead.
Input.Focus();
}
public string Message { get; set; } = "";
public string MainText { get; set; } = "";
public string SecondaryText { get; set; } = "";
@@ -54,31 +62,10 @@ namespace Ryujinx.Ava.UI.Controls
UserResult result = UserResult.Cancel;
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText)
{
Message = args.InitialText ?? ""
};
SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText, args.InitialText);
string input = string.Empty;
var overlay = new ContentDialogOverlayWindow()
{
Height = window.Bounds.Height,
Width = window.Bounds.Width,
Position = window.PointToScreen(new Point())
};
window.PositionChanged += OverlayOnPositionChanged;
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
{
overlay.Position = window.PointToScreen(new Point());
}
contentDialog = overlay.ContentDialog;
bool opened = false;
content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
content._host = contentDialog;
@@ -99,25 +86,7 @@ namespace Ryujinx.Ava.UI.Controls
};
contentDialog.Closed += handler;
overlay.Opened += OverlayOnActivated;
async void OverlayOnActivated(object sender, EventArgs e)
{
if (opened)
{
return;
}
opened = true;
overlay.Position = window.PointToScreen(new Point());
await contentDialog.ShowAsync(overlay);
contentDialog.Closed -= handler;
overlay.Close();
};
await overlay.ShowDialog(window);
await ContentDialogHelper.ShowAsync(contentDialog);
return (result, input);
}

View File

@@ -1,32 +0,0 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Controls.InputDialog"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Focusable="True">
<Grid
Margin="5,10,5,5"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center" Text="{Binding Message}" />
<TextBox
Grid.Row="1"
Width="300"
Margin="10"
HorizontalAlignment="Center"
MaxLength="{Binding MaxLength}"
Text="{Binding Input, Mode=TwoWay}" />
<TextBlock
Grid.Row="2"
Margin="5,5,5,10"
HorizontalAlignment="Center"
Text="{Binding SubMessage}" />
</Grid>
</UserControl>

View File

@@ -1,57 +0,0 @@
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Controls
{
public partial class InputDialog : UserControl
{
public string Message { get; set; }
public string Input { get; set; }
public string SubMessage { get; set; }
public uint MaxLength { get; }
public InputDialog(string message, string input = "", string subMessage = "", uint maxLength = int.MaxValue)
{
Message = message;
Input = input;
SubMessage = subMessage;
MaxLength = maxLength;
DataContext = this;
}
public InputDialog()
{
InitializeComponent();
}
public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, string message,
string input = "", string subMessage = "", uint maxLength = int.MaxValue)
{
UserResult result = UserResult.Cancel;
InputDialog content = new InputDialog(message, input, subMessage, maxLength);
ContentDialog contentDialog = new ContentDialog
{
Title = title,
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.InputDialogOk],
SecondaryButtonText = "",
CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel],
Content = content,
PrimaryButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.Ok;
input = content.Input;
})
};
await contentDialog.ShowAsync();
return (result, input);
}
}
}

View File

@@ -1,15 +1,26 @@
using Avalonia.Controls;
using Ryujinx.Ava.UI.Windows;
using System.Threading;
namespace Ryujinx.Ava.UI.Controls
{
public partial class UpdateWaitWindow : StyleableWindow
{
public UpdateWaitWindow(string primaryText, string secondaryText, CancellationTokenSource cancellationToken) : this(primaryText, secondaryText)
{
SystemDecorations = SystemDecorations.Full;
ShowInTaskbar = true;
Closing += (_, _) => cancellationToken.Cancel();
}
public UpdateWaitWindow(string primaryText, string secondaryText) : this()
{
PrimaryText.Text = primaryText;
SecondaryText.Text = secondaryText;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
SystemDecorations = SystemDecorations.BorderOnly;
ShowInTaskbar = false;
}
public UpdateWaitWindow()

View File

@@ -27,7 +27,6 @@ namespace Ryujinx.Ava.UI.Helpers
string closeButton,
UserResult primaryButtonResult = UserResult.Ok,
ManualResetEvent deferResetEvent = null,
Func<Window, Task> doWhileDeferred = null,
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
{
UserResult result = UserResult.None;
@@ -78,12 +77,11 @@ namespace Ryujinx.Ava.UI.Helpers
int iconSymbol,
UserResult primaryButtonResult = UserResult.Ok,
ManualResetEvent deferResetEvent = null,
Func<Window, Task> doWhileDeferred = null,
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
{
Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol);
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, doWhileDeferred, deferCloseAction);
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction);
}
public async static Task<UserResult> ShowDeferredContentDialog(
@@ -111,7 +109,6 @@ namespace Ryujinx.Ava.UI.Helpers
iconSymbol,
primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok,
deferResetEvent,
doWhileDeferred,
DeferClose);
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
@@ -236,11 +233,6 @@ namespace Ryujinx.Ava.UI.Helpers
primaryButtonResult);
}
internal static UpdateWaitWindow CreateWaitingDialog(string mainText, string secondaryText)
{
return new(mainText, secondaryText);
}
internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
{
await ShowTextDialog(
@@ -319,28 +311,6 @@ namespace Ryujinx.Ava.UI.Helpers
LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]);
}
internal static async Task<string> CreateInputDialog(
string title,
string mainText,
string subText,
uint maxLength = int.MaxValue,
string input = "")
{
var result = await InputDialog.ShowInputDialog(
title,
mainText,
input,
subText,
maxLength);
if (result.Result == UserResult.Ok)
{
return result.Input;
}
return string.Empty;
}
public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog)
{
ContentDialogResult result;

View File

@@ -7,6 +7,7 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Models.Amiibo;
using System;
@@ -42,13 +43,18 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _showAllAmiibo;
private bool _useRandomUuid;
private string _usage;
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
{
_owner = owner;
_httpClient = new HttpClient { Timeout = TimeSpan.FromMilliseconds(5000) };
_httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30)
};
LastScannedAmiiboId = lastScannedAmiiboId;
TitleId = titleId;
@@ -89,9 +95,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
_showAllAmiibo = value;
#pragma warning disable 4014
ParseAmiiboData();
#pragma warning restore 4014
OnPropertyChanged();
}
@@ -203,8 +207,10 @@ namespace Ryujinx.Ava.UI.ViewModels
{
amiiboJsonString = await DownloadAmiiboJson();
}
catch
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data: {ex}");
ShowInfoDialog();
}
}
@@ -369,8 +375,10 @@ namespace Ryujinx.Ava.UI.ViewModels
return false;
}
catch
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to check for amiibo updates: {ex}");
ShowInfoDialog();
return false;
@@ -393,6 +401,8 @@ namespace Ryujinx.Ava.UI.ViewModels
return amiiboJsonString;
}
Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}");
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage],
LocaleManager.Instance[LocaleKeys.InputDialogOk],
@@ -429,6 +439,10 @@ namespace Ryujinx.Ava.UI.ViewModels
AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(resizeWidth, resizeHeight));
}
}
else
{
Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}");
}
}
private void ResetAmiiboPreview()

View File

@@ -972,7 +972,7 @@ namespace Ryujinx.Ava.UI.ViewModels
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
UpdateWaitWindow waitingDialog = ContentDialogHelper.CreateWaitingDialog(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]);
UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]);
if (result == UserResult.Yes)
{
@@ -1336,6 +1336,23 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public void ToggleFileType(string fileType)
{
_ = fileType switch
{
"NSP" => ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP,
"PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0,
"XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI,
"NCA" => ConfigurationState.Instance.Ui.ShownFileTypes.NCA.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NCA,
"NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO,
"NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO,
_ => throw new ArgumentOutOfRangeException(fileType),
};
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
LoadApplications();
}
public async void ManageProfiles()
{
await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient);

View File

@@ -23,6 +23,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Net.NetworkInformation;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
namespace Ryujinx.Ava.UI.ViewModels
@@ -35,6 +36,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly List<string> _validTzRegions;
private readonly Dictionary<string, string> _networkInterfaces;
private float _customResolutionScale;
private int _resolutionScale;
private int _graphicsBackendMultithreadingIndex;
@@ -50,6 +53,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public event Action CloseWindow;
public event Action SaveSettingsEvent;
private int _networkInterfaceIndex;
public int ResolutionScale
{
@@ -240,6 +244,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public AvaloniaList<string> GameDirectories { get; set; }
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
public AvaloniaList<string> NetworkInterfaceList
{
get => new AvaloniaList<string>(_networkInterfaces.Keys);
}
public KeyboardHotkeys KeyboardHotkeys
{
get => _keyboardHotkeys;
@@ -251,6 +260,16 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public int NetworkInterfaceIndex
{
get => _networkInterfaceIndex;
set
{
_networkInterfaceIndex = value != -1 ? value : 0;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]];
}
}
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{
_virtualFileSystem = virtualFileSystem;
@@ -267,8 +286,10 @@ namespace Ryujinx.Ava.UI.ViewModels
TimeZones = new AvaloniaList<TimeZone>();
AvailableGpus = new ObservableCollection<ComboBoxItem>();
_validTzRegions = new List<string>();
_networkInterfaces = new Dictionary<string, string>();
CheckSoundBackends();
PopulateNetworkInterfaces();
if (Program.PreviewerDetached)
{
@@ -327,6 +348,17 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
private void PopulateNetworkInterfaces()
{
_networkInterfaces.Clear();
_networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0");
foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
{
_networkInterfaces.Add(networkInterface.Name, networkInterface.Id);
}
}
public void ValidateAndSetTimeZone(string location)
{
if (_validTzRegions.Contains(location))
@@ -414,6 +446,8 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(config.Multiplayer.LanInterfaceId.Value);
}
public void SaveSettings()
@@ -515,6 +549,8 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig();

View File

@@ -78,6 +78,7 @@
</MenuItem>
<Separator />
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
<MenuItem Name="ToggleFileTypesMenuItem" Header="{locale:Locale MenuBarShowFileTypes}" />
<Separator />
<MenuItem
Click="OpenSettings"

View File

@@ -11,6 +11,8 @@ using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS;
using Ryujinx.Modules;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Collections.Generic;
@@ -29,6 +31,30 @@ namespace Ryujinx.Ava.UI.Views.Main
{
InitializeComponent();
ToggleFileTypesMenuItem.Items = GenerateToggleFileTypeItems();
ChangeLanguageMenuItem.Items = GenerateLanguageMenuItems();
}
private CheckBox[] GenerateToggleFileTypeItems()
{
List<CheckBox> checkBoxes = new();
foreach (var item in Enum.GetValues(typeof (FileTypes)))
{
string fileName = Enum.GetName(typeof (FileTypes), item);
checkBoxes.Add(new CheckBox()
{
Content = $".{fileName}",
IsChecked = ((FileTypes)item).GetConfigValue(ConfigurationState.Instance.Ui.ShownFileTypes),
Command = MiniCommand.Create(() => ViewModel.ToggleFileType(fileName))
});
}
return checkBoxes.ToArray();
}
private MenuItem[] GenerateLanguageMenuItems()
{
List<MenuItem> menuItems = new();
string localePath = "Ryujinx.Ava/Assets/Locales";
@@ -61,7 +87,7 @@ namespace Ryujinx.Ava.UI.Views.Main
menuItems.Add(menuItem);
}
ChangeLanguageMenuItem.Items = menuItems.ToArray();
return menuItems.ToArray();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)

View File

@@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsNetworkView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -29,7 +29,18 @@
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
</CheckBox>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabNetworkInterface}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
Width="200" />
<ComboBox SelectedIndex="{Binding NetworkInterfaceIndex}"
ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}"
HorizontalContentAlignment="Left"
Items="{Binding NetworkInterfaceList}"
Width="250" />
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>
</UserControl>

View File

@@ -1,4 +1,4 @@
<window:StyleableWindow
<window:StyleableWindow
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -10,20 +10,16 @@
d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Windows.ContentDialogOverlayWindow"
Title="ContentDialogOverlayWindow"
Focusable="True">
Focusable="False">
<window:StyleableWindow.Styles>
<Style Selector="ui|ContentDialog /template/ Panel#LayoutRoot">
<Setter Property="Background"
Value="Transparent" />
</Style>
</window:StyleableWindow.Styles>
<ContentControl
Focusable="False"
IsVisible="False"
KeyboardNavigation.IsTabStop="False">
<ui:ContentDialog Name="ContentDialog"
IsPrimaryButtonEnabled="True"
IsSecondaryButtonEnabled="True"
IsVisible="False" />
</ContentControl>
<ui:ContentDialog Name="ContentDialog"
IsPrimaryButtonEnabled="True"
IsSecondaryButtonEnabled="True"
IsVisible="False"
Focusable="True"/>
</window:StyleableWindow>

View File

@@ -0,0 +1,51 @@
using System;
using System.Buffers;
using System.Threading;
namespace Ryujinx.Common.Memory
{
public sealed 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

@@ -0,0 +1,108 @@
using System;
using System.Buffers;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// Provides a pool of re-usable byte array instances.
/// </summary>
public sealed partial class ByteMemoryPool
{
private static readonly ByteMemoryPool _shared = new ByteMemoryPool();
/// <summary>
/// Constructs a <see cref="ByteMemoryPool"/> instance. Private to force access through
/// the <see cref="ByteMemoryPool.Shared"/> instance.
/// </summary>
private ByteMemoryPool()
{
// No implementation
}
/// <summary>
/// Retrieves a shared <see cref="ByteMemoryPool"/> instance.
/// </summary>
public static ByteMemoryPool Shared => _shared;
/// <summary>
/// Returns the maximum buffer size supported by this pool.
/// </summary>
public 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 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 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 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 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 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 IMemoryOwner<byte> RentCleared(int length)
{
var buffer = RentImpl(length);
buffer.Memory.Span.Clear();
return buffer;
}
private static ByteMemoryPoolBuffer RentImpl(int length)
{
if ((uint)length > Array.MaxLength)
{
throw new ArgumentOutOfRangeException(nameof(length), length, null);
}
return new ByteMemoryPoolBuffer(length);
}
}
}

View File

@@ -33,6 +33,11 @@ namespace Ryujinx.Common.Memory
return data;
}
public ReadOnlySpan<byte> GetSpanSafe(int size)
{
return GetSpan((int)Math.Min((uint)_input.Length, (uint)size));
}
public T ReadAt<T>(int offset) where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(_input.Slice(offset))[0];

View File

@@ -0,0 +1,66 @@
using System.Net.NetworkInformation;
namespace Ryujinx.Common.Utilities
{
public static class NetworkHelpers
{
private static (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(NetworkInterface adapter, bool isPreferred)
{
IPInterfaceProperties properties = adapter.GetIPProperties();
if (isPreferred || (properties.GatewayAddresses.Count > 0 && properties.DnsAddresses.Count > 0))
{
foreach (UnicastIPAddressInformation info in properties.UnicastAddresses)
{
// Only accept an IPv4 address
if (info.Address.GetAddressBytes().Length == 4)
{
return (properties, info);
}
}
}
return (null, null);
}
public static (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(string lanInterfaceId = "0")
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
return (null, null);
}
IPInterfaceProperties targetProperties = null;
UnicastIPAddressInformation targetAddressInfo = null;
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
string guid = lanInterfaceId;
bool hasPreference = guid != "0";
foreach (NetworkInterface adapter in interfaces)
{
bool isPreferred = adapter.Id == guid;
// Ignore loopback and non IPv4 capable interface.
if (isPreferred || (targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4)))
{
(IPInterfaceProperties properties, UnicastIPAddressInformation info) = GetLocalInterface(adapter, isPreferred);
if (properties != null)
{
targetProperties = properties;
targetAddressInfo = info;
if (isPreferred || !hasPreference)
{
break;
}
}
}
}
return (targetProperties, targetAddressInfo);
}
}
}

View File

@@ -35,7 +35,8 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsNonConstantTextureOffset;
public readonly bool SupportsShaderBallot;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsViewportIndex;
public readonly bool SupportsViewportIndexVertexTessellation;
public readonly bool SupportsViewportMask;
public readonly bool SupportsViewportSwizzle;
public readonly bool SupportsIndirectParameters;
@@ -48,6 +49,8 @@ namespace Ryujinx.Graphics.GAL
public readonly float MaximumSupportedAnisotropy;
public readonly int StorageBufferOffsetAlignment;
public readonly int GatherBiasPrecision;
public Capabilities(
TargetApi api,
string vendorName,
@@ -78,7 +81,8 @@ namespace Ryujinx.Graphics.GAL
bool supportsNonConstantTextureOffset,
bool supportsShaderBallot,
bool supportsTextureShadowLod,
bool supportsViewportIndex,
bool supportsViewportIndexVertexTessellation,
bool supportsViewportMask,
bool supportsViewportSwizzle,
bool supportsIndirectParameters,
uint maximumUniformBuffersPerStage,
@@ -87,7 +91,8 @@ namespace Ryujinx.Graphics.GAL
uint maximumImagesPerStage,
int maximumComputeSharedMemorySize,
float maximumSupportedAnisotropy,
int storageBufferOffsetAlignment)
int storageBufferOffsetAlignment,
int gatherBiasPrecision)
{
Api = api;
VendorName = vendorName;
@@ -118,7 +123,8 @@ namespace Ryujinx.Graphics.GAL
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
SupportsShaderBallot = supportsShaderBallot;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsViewportIndex = supportsViewportIndex;
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
SupportsViewportMask = supportsViewportMask;
SupportsViewportSwizzle = supportsViewportSwizzle;
SupportsIndirectParameters = supportsIndirectParameters;
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
@@ -128,6 +134,7 @@ namespace Ryujinx.Graphics.GAL
MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize;
MaximumSupportedAnisotropy = maximumSupportedAnisotropy;
StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
GatherBiasPrecision = gatherBiasPrecision;
}
}
}

View File

@@ -6,6 +6,7 @@ using Ryujinx.Graphics.Texture;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Graphics.Gpu.Engine.Dma
@@ -32,6 +33,69 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
RemapEnable = 1 << 10
}
/// <summary>
/// Texture parameters for copy.
/// </summary>
private struct TextureParams
{
/// <summary>
/// Copy region X coordinate.
/// </summary>
public readonly int RegionX;
/// <summary>
/// Copy region Y coordinate.
/// </summary>
public readonly int RegionY;
/// <summary>
/// Offset from the base pointer of the data in memory.
/// </summary>
public readonly int BaseOffset;
/// <summary>
/// Bytes per pixel.
/// </summary>
public readonly int Bpp;
/// <summary>
/// Whether the texture is linear. If false, the texture is block linear.
/// </summary>
public readonly bool Linear;
/// <summary>
/// Pixel offset from XYZ coordinates calculator.
/// </summary>
public readonly OffsetCalculator Calculator;
/// <summary>
/// Creates texture parameters.
/// </summary>
/// <param name="regionX">Copy region X coordinate</param>
/// <param name="regionY">Copy region Y coordinate</param>
/// <param name="baseOffset">Offset from the base pointer of the data in memory</param>
/// <param name="bpp">Bytes per pixel</param>
/// <param name="linear">Whether the texture is linear. If false, the texture is block linear</param>
/// <param name="calculator">Pixel offset from XYZ coordinates calculator</param>
public TextureParams(int regionX, int regionY, int baseOffset, int bpp, bool linear, OffsetCalculator calculator)
{
RegionX = regionX;
RegionY = regionY;
BaseOffset = baseOffset;
Bpp = bpp;
Linear = linear;
Calculator = calculator;
}
}
[StructLayout(LayoutKind.Sequential, Size = 3, Pack = 1)]
private struct UInt24
{
public byte Byte0;
public byte Byte1;
public byte Byte2;
}
/// <summary>
/// Creates a new instance of the DMA copy engine class.
/// </summary>
@@ -154,8 +218,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
{
// Buffer to texture copy.
int componentSize = (int)_state.State.SetRemapComponentsComponentSize + 1;
int srcBpp = remap ? ((int)_state.State.SetRemapComponentsNumSrcComponents + 1) * componentSize : 1;
int dstBpp = remap ? ((int)_state.State.SetRemapComponentsNumDstComponents + 1) * componentSize : 1;
int srcComponents = (int)_state.State.SetRemapComponentsNumSrcComponents + 1;
int dstComponents = (int)_state.State.SetRemapComponentsNumDstComponents + 1;
int srcBpp = remap ? srcComponents * componentSize : 1;
int dstBpp = remap ? dstComponents * componentSize : 1;
var dst = Unsafe.As<uint, DmaTexture>(ref _state.State.SetDstBlockSize);
var src = Unsafe.As<uint, DmaTexture>(ref _state.State.SetSrcBlockSize);
@@ -274,63 +340,51 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
}
}
unsafe bool Convert<T>(Span<byte> dstSpan, ReadOnlySpan<byte> srcSpan) where T : unmanaged
{
if (srcLinear && dstLinear && srcBpp == dstBpp)
{
// Optimized path for purely linear copies - we don't need to calculate every single byte offset,
// and we can make use of Span.CopyTo which is very very fast (even compared to pointers)
for (int y = 0; y < yCount; y++)
{
srcCalculator.SetY(srcRegionY + y);
dstCalculator.SetY(dstRegionY + y);
int srcOffset = srcCalculator.GetOffset(srcRegionX);
int dstOffset = dstCalculator.GetOffset(dstRegionX);
srcSpan.Slice(srcOffset - srcBaseOffset, xCount * srcBpp)
.CopyTo(dstSpan.Slice(dstOffset - dstBaseOffset, xCount * dstBpp));
}
}
else
{
fixed (byte* dstPtr = dstSpan, srcPtr = srcSpan)
{
byte* dstBase = dstPtr - dstBaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
byte* srcBase = srcPtr - srcBaseOffset;
for (int y = 0; y < yCount; y++)
{
srcCalculator.SetY(srcRegionY + y);
dstCalculator.SetY(dstRegionY + y);
for (int x = 0; x < xCount; x++)
{
int srcOffset = srcCalculator.GetOffset(srcRegionX + x);
int dstOffset = dstCalculator.GetOffset(dstRegionX + x);
*(T*)(dstBase + dstOffset) = *(T*)(srcBase + srcOffset);
}
}
}
}
return true;
}
// OPT: This allocates a (potentially) huge temporary array and then copies an existing
// region of memory into it, data that might get overwritten entirely anyways. Ideally this should
// all be rewritten to use pooled arrays, but that gets complicated with packed data and strides
Span<byte> dstSpan = memoryManager.GetSpan(dstGpuVa + (ulong)dstBaseOffset, dstSize).ToArray();
bool _ = srcBpp switch
TextureParams srcParams = new TextureParams(srcRegionX, srcRegionY, srcBaseOffset, srcBpp, srcLinear, srcCalculator);
TextureParams dstParams = new TextureParams(dstRegionX, dstRegionY, dstBaseOffset, dstBpp, dstLinear, dstCalculator);
// If remapping is enabled, we always copy the components directly, in order.
// If it's enabled, but the mapping is just XYZW, we also copy them in order.
bool isIdentityRemap = !remap ||
(_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX &&
(dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) &&
(dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) &&
(dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW));
if (isIdentityRemap)
{
1 => Convert<byte>(dstSpan, srcSpan),
2 => Convert<ushort>(dstSpan, srcSpan),
4 => Convert<uint>(dstSpan, srcSpan),
8 => Convert<ulong>(dstSpan, srcSpan),
12 => Convert<Bpp12Pixel>(dstSpan, srcSpan),
16 => Convert<Vector128<byte>>(dstSpan, srcSpan),
_ => throw new NotSupportedException($"Unable to copy ${srcBpp} bpp pixel format.")
};
// The order of the components doesn't change, so we can just copy directly
// (with layout conversion if necessary).
switch (srcBpp)
{
case 1: Copy<byte>(dstSpan, srcSpan, dstParams, srcParams); break;
case 2: Copy<ushort>(dstSpan, srcSpan, dstParams, srcParams); break;
case 4: Copy<uint>(dstSpan, srcSpan, dstParams, srcParams); break;
case 8: Copy<ulong>(dstSpan, srcSpan, dstParams, srcParams); break;
case 12: Copy<Bpp12Pixel>(dstSpan, srcSpan, dstParams, srcParams); break;
case 16: Copy<Vector128<byte>>(dstSpan, srcSpan, dstParams, srcParams); break;
default: throw new NotSupportedException($"Unable to copy ${srcBpp} bpp pixel format.");
}
}
else
{
// The order or value of the components might change.
switch (componentSize)
{
case 1: CopyShuffle<byte>(dstSpan, srcSpan, dstParams, srcParams); break;
case 2: CopyShuffle<ushort>(dstSpan, srcSpan, dstParams, srcParams); break;
case 3: CopyShuffle<UInt24>(dstSpan, srcSpan, dstParams, srcParams); break;
case 4: CopyShuffle<uint>(dstSpan, srcSpan, dstParams, srcParams); break;
default: throw new NotSupportedException($"Unable to copy ${componentSize} component size.");
}
}
memoryManager.Write(dstGpuVa + (ulong)dstBaseOffset, dstSpan);
}
@@ -372,6 +426,133 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
}
}
/// <summary>
/// Copies data from one texture to another, while performing layout conversion if necessary.
/// </summary>
/// <typeparam name="T">Pixel type</typeparam>
/// <param name="dstSpan">Destination texture memory region</param>
/// <param name="srcSpan">Source texture memory region</param>
/// <param name="dst">Destination texture parameters</param>
/// <param name="src">Source texture parameters</param>
private unsafe void Copy<T>(Span<byte> dstSpan, ReadOnlySpan<byte> srcSpan, TextureParams dst, TextureParams src) where T : unmanaged
{
int xCount = (int)_state.State.LineLengthIn;
int yCount = (int)_state.State.LineCount;
if (src.Linear && dst.Linear && src.Bpp == dst.Bpp)
{
// Optimized path for purely linear copies - we don't need to calculate every single byte offset,
// and we can make use of Span.CopyTo which is very very fast (even compared to pointers)
for (int y = 0; y < yCount; y++)
{
src.Calculator.SetY(src.RegionY + y);
dst.Calculator.SetY(dst.RegionY + y);
int srcOffset = src.Calculator.GetOffset(src.RegionX);
int dstOffset = dst.Calculator.GetOffset(dst.RegionX);
srcSpan.Slice(srcOffset - src.BaseOffset, xCount * src.Bpp)
.CopyTo(dstSpan.Slice(dstOffset - dst.BaseOffset, xCount * dst.Bpp));
}
}
else
{
fixed (byte* dstPtr = dstSpan, srcPtr = srcSpan)
{
byte* dstBase = dstPtr - dst.BaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
byte* srcBase = srcPtr - src.BaseOffset;
for (int y = 0; y < yCount; y++)
{
src.Calculator.SetY(src.RegionY + y);
dst.Calculator.SetY(dst.RegionY + y);
for (int x = 0; x < xCount; x++)
{
int srcOffset = src.Calculator.GetOffset(src.RegionX + x);
int dstOffset = dst.Calculator.GetOffset(dst.RegionX + x);
*(T*)(dstBase + dstOffset) = *(T*)(srcBase + srcOffset);
}
}
}
}
}
/// <summary>
/// Sets texture pixel data to a constant value, while performing layout conversion if necessary.
/// </summary>
/// <typeparam name="T">Pixel type</typeparam>
/// <param name="dstSpan">Destination texture memory region</param>
/// <param name="dst">Destination texture parameters</param>
/// <param name="fillValue">Constant pixel value to be set</param>
private unsafe void Fill<T>(Span<byte> dstSpan, TextureParams dst, T fillValue) where T : unmanaged
{
int xCount = (int)_state.State.LineLengthIn;
int yCount = (int)_state.State.LineCount;
fixed (byte* dstPtr = dstSpan)
{
byte* dstBase = dstPtr - dst.BaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
for (int y = 0; y < yCount; y++)
{
dst.Calculator.SetY(dst.RegionY + y);
for (int x = 0; x < xCount; x++)
{
int dstOffset = dst.Calculator.GetOffset(dst.RegionX + x);
*(T*)(dstBase + dstOffset) = fillValue;
}
}
}
}
/// <summary>
/// Copies data from one texture to another, while performing layout conversion and component shuffling if necessary.
/// </summary>
/// <typeparam name="T">Pixel type</typeparam>
/// <param name="dstSpan">Destination texture memory region</param>
/// <param name="srcSpan">Source texture memory region</param>
/// <param name="dst">Destination texture parameters</param>
/// <param name="src">Source texture parameters</param>
private void CopyShuffle<T>(Span<byte> dstSpan, ReadOnlySpan<byte> srcSpan, TextureParams dst, TextureParams src) where T : unmanaged
{
int dstComponents = (int)_state.State.SetRemapComponentsNumDstComponents + 1;
for (int i = 0; i < dstComponents; i++)
{
SetRemapComponentsDst componentsDst = i switch
{
0 => _state.State.SetRemapComponentsDstX,
1 => _state.State.SetRemapComponentsDstY,
2 => _state.State.SetRemapComponentsDstZ,
_ => _state.State.SetRemapComponentsDstW
};
switch (componentsDst)
{
case SetRemapComponentsDst.SrcX:
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan, dst, src);
break;
case SetRemapComponentsDst.SrcY:
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan.Slice(Unsafe.SizeOf<T>()), dst, src);
break;
case SetRemapComponentsDst.SrcZ:
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan.Slice(Unsafe.SizeOf<T>() * 2), dst, src);
break;
case SetRemapComponentsDst.SrcW:
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan.Slice(Unsafe.SizeOf<T>() * 3), dst, src);
break;
case SetRemapComponentsDst.ConstA:
Fill<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), dst, Unsafe.As<uint, T>(ref _state.State.SetRemapConstA));
break;
case SetRemapComponentsDst.ConstB:
Fill<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), dst, Unsafe.As<uint, T>(ref _state.State.SetRemapConstB));
break;
}
}
}
/// <summary>
/// Copies block linear data with block linear GOBs to a block linear destination with linear GOBs.
/// </summary>

View File

@@ -732,12 +732,7 @@ namespace Ryujinx.Graphics.Gpu.Image
break;
case Target.Texture2DMultisample:
case Target.Texture2DMultisampleArray:
// We don't support copy between multisample and non-multisample depth-stencil textures
// because there's no way to emulate that since most GPUs don't support writing a
// custom stencil value into the texture, among several other API limitations.
if ((rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray) &&
!rhs.FormatInfo.Format.IsDepthOrStencil())
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
{
return TextureViewCompatibility.CopyOnly;
}

View File

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

View File

@@ -112,6 +112,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
};
}
public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision;
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;
public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug;
@@ -142,7 +144,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
public bool QueryHostSupportsViewportIndex() => _context.Capabilities.SupportsViewportIndex;
public bool QueryHostSupportsViewportIndexVertexTessellation() => _context.Capabilities.SupportsViewportIndexVertexTessellation;
public bool QueryHostSupportsViewportMask() => _context.Capabilities.SupportsViewportMask;
/// <summary>
/// Converts a packed Maxwell texture format to the shader translator texture format.

View File

@@ -449,7 +449,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (translatorContexts[i] != null)
{
translatorContexts[i].SetGeometryShaderLayerInputAttribute(info.GpLayerInputAttribute);
translatorContexts[i].SetLastInVertexPipeline(translatorContexts[5] != null);
translatorContexts[i].SetLastInVertexPipeline();
break;
}
}

View File

@@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.OpenGL
private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture"));
private static readonly Lazy<bool> _supportsShaderBallot = new Lazy<bool>(() => HasExtension("GL_ARB_shader_ballot"));
private static readonly Lazy<bool> _supportsShaderViewportLayerArray = new Lazy<bool>(() => HasExtension("GL_ARB_shader_viewport_layer_array"));
private static readonly Lazy<bool> _supportsViewportArray2 = new Lazy<bool>(() => HasExtension("GL_NV_viewport_array2"));
private static readonly Lazy<bool> _supportsTextureCompressionBptc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_bptc"));
private static readonly Lazy<bool> _supportsTextureCompressionRgtc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_rgtc"));
private static readonly Lazy<bool> _supportsTextureCompressionS3tc = new Lazy<bool>(() => HasExtension("GL_EXT_texture_compression_s3tc"));
@@ -65,6 +66,7 @@ namespace Ryujinx.Graphics.OpenGL
public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
public static bool SupportsShaderBallot => _supportsShaderBallot.Value;
public static bool SupportsShaderViewportLayerArray => _supportsShaderViewportLayerArray.Value;
public static bool SupportsViewportArray2 => _supportsViewportArray2.Value;
public static bool SupportsTextureCompressionBptc => _supportsTextureCompressionBptc.Value;
public static bool SupportsTextureCompressionRgtc => _supportsTextureCompressionRgtc.Value;
public static bool SupportsTextureCompressionS3tc => _supportsTextureCompressionS3tc.Value;

View File

@@ -0,0 +1,103 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.OpenGL.Image
{
class IntermediatePool : IDisposable
{
private readonly OpenGLRenderer _renderer;
private readonly List<TextureView> _entries;
public IntermediatePool(OpenGLRenderer renderer)
{
_renderer = renderer;
_entries = new List<TextureView>();
}
public TextureView GetOrCreateWithAtLeast(
Target target,
int blockWidth,
int blockHeight,
int bytesPerPixel,
Format format,
int width,
int height,
int depth,
int levels,
int samples)
{
TextureView entry;
for (int i = 0; i < _entries.Count; i++)
{
entry = _entries[i];
if (entry.Target == target && entry.Format == format && entry.Info.Samples == samples)
{
if (entry.Width < width ||
entry.Height < height ||
entry.Info.Depth < depth ||
entry.Info.Levels < levels)
{
width = Math.Max(width, entry.Width);
height = Math.Max(height, entry.Height);
depth = Math.Max(depth, entry.Info.Depth);
levels = Math.Max(levels, entry.Info.Levels);
entry.Dispose();
entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels, samples);
_entries[i] = entry;
}
return entry;
}
}
entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels, samples);
_entries.Add(entry);
return entry;
}
private TextureView CreateNew(
Target target,
int blockWidth,
int blockHeight,
int bytesPerPixel,
Format format,
int width,
int height,
int depth,
int levels,
int samples)
{
return (TextureView)_renderer.CreateTexture(new TextureCreateInfo(
width,
height,
depth,
levels,
samples,
blockWidth,
blockHeight,
bytesPerPixel,
format,
DepthStencilMode.Depth,
target,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha), 1f);
}
public void Dispose()
{
foreach (TextureView entry in _entries)
{
entry.Dispose();
}
_entries.Clear();
}
}
}

View File

@@ -15,9 +15,12 @@ namespace Ryujinx.Graphics.OpenGL.Image
private int _copyPboHandle;
private int _copyPboSize;
public IntermediatePool IntermediatePool { get; }
public TextureCopy(OpenGLRenderer renderer)
{
_renderer = renderer;
IntermediatePool = new IntermediatePool(renderer);
}
public void Copy(
@@ -514,6 +517,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
_copyPboHandle = 0;
}
IntermediatePool.Dispose();
}
}
}

View File

@@ -117,12 +117,20 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
TextureView destinationView = (TextureView)destination;
if (!destinationView.Target.IsMultisample() && Target.IsMultisample())
bool srcIsMultisample = Target.IsMultisample();
bool dstIsMultisample = destinationView.Target.IsMultisample();
if (dstIsMultisample != srcIsMultisample && Info.Format.IsDepthOrStencil())
{
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
CopyWithBlitForDepthMS(destinationView, 0, firstLayer, layers);
}
else if (!dstIsMultisample && srcIsMultisample)
{
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
_renderer.TextureCopyMS.CopyMSToNonMS(this, destinationView, 0, firstLayer, layers);
}
else if (destinationView.Target.IsMultisample() && !Target.IsMultisample())
else if (dstIsMultisample && !srcIsMultisample)
{
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
_renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, 0, firstLayer, layers);
@@ -143,11 +151,18 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
TextureView destinationView = (TextureView)destination;
if (!destinationView.Target.IsMultisample() && Target.IsMultisample())
bool srcIsMultisample = Target.IsMultisample();
bool dstIsMultisample = destinationView.Target.IsMultisample();
if (dstIsMultisample != srcIsMultisample && Info.Format.IsDepthOrStencil())
{
CopyWithBlitForDepthMS(destinationView, srcLayer, dstLayer, 1);
}
else if (!dstIsMultisample && srcIsMultisample)
{
_renderer.TextureCopyMS.CopyMSToNonMS(this, destinationView, srcLayer, dstLayer, 1);
}
else if (destinationView.Target.IsMultisample() && !Target.IsMultisample())
else if (dstIsMultisample && !srcIsMultisample)
{
_renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, srcLayer, dstLayer, 1);
}
@@ -161,6 +176,61 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
private void CopyWithBlitForDepthMS(TextureView destinationView, int srcLayer, int dstLayer, int layers)
{
// This is currently used for multisample <-> non-multisample copies.
// We can't do that with compute because it's not possible to write depth textures on compute.
// It can be done with draws, but we don't have support for saving and restoring the OpenGL state
// for a draw with different state right now.
// This approach uses blit, which causes a resolution loss since some samples will be lost
// in the process.
Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
if (destinationView.Target.IsMultisample())
{
TextureView intermmediate = _renderer.TextureCopy.IntermediatePool.GetOrCreateWithAtLeast(
Info.Target,
Info.BlockWidth,
Info.BlockHeight,
Info.BytesPerPixel,
Format,
destinationView.Width,
destinationView.Height,
Info.Depth,
1,
1);
_renderer.TextureCopy.Copy(this, intermmediate, srcRegion, dstRegion, false);
_renderer.TextureCopy.Copy(intermmediate, destinationView, dstRegion, dstRegion, false, srcLayer, dstLayer, 0, 0, layers, 1);
}
else
{
Target target = Target switch
{
Target.Texture2DMultisample => Target.Texture2D,
Target.Texture2DMultisampleArray => Target.Texture2DArray,
_ => Target
};
TextureView intermmediate = _renderer.TextureCopy.IntermediatePool.GetOrCreateWithAtLeast(
target,
Info.BlockWidth,
Info.BlockHeight,
Info.BytesPerPixel,
Format,
Width,
Height,
Info.Depth,
1,
1);
_renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, false);
_renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, false, srcLayer, dstLayer, 0, 0, layers, 1);
}
}
public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
{
_renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter);

View File

@@ -103,11 +103,14 @@ namespace Ryujinx.Graphics.OpenGL
public Capabilities GetCapabilities()
{
bool intelWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows;
bool amdWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows;
return new Capabilities(
api: TargetApi.OpenGL,
vendorName: GpuVendor,
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
hasFrontFacingBug: intelWindows,
hasVectorIndexingBug: amdWindows,
needsFragmentOutputSpecialization: false,
reduceShaderPrecision: false,
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
@@ -133,7 +136,8 @@ namespace Ryujinx.Graphics.OpenGL
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
supportsViewportIndex: HwCapabilities.SupportsShaderViewportLayerArray,
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
@@ -142,7 +146,8 @@ namespace Ryujinx.Graphics.OpenGL
maximumImagesPerStage: 8,
maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize,
maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy,
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment);
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment,
gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0); // Precision is 8 for these vendors on Vulkan.
}
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)

View File

@@ -226,6 +226,7 @@ namespace Ryujinx.Graphics.OpenGL
// Set clip control, viewport and the framebuffer to the output to placate overlays and OBS capture.
GL.ClipControl(ClipOrigin.LowerLeft, ClipDepthMode.NegativeOneToOne);
GL.Viewport(0, 0, _width, _height);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, drawFramebuffer);
swapBuffersCallback();

View File

@@ -59,6 +59,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine("#extension GL_NV_geometry_shader_passthrough : enable");
}
if (context.Config.GpuAccessor.QueryHostSupportsViewportMask())
{
context.AppendLine("#extension GL_NV_viewport_array2 : enable");
}
context.AppendLine("#pragma optionNV(fastmath off)");
context.AppendLine();
@@ -215,7 +220,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
{
var tfOutput = context.Info.GetTransformFeedbackOutput(AttributeConsts.PositionX);
var tfOutput = context.Config.GetTransformFeedbackOutput(AttributeConsts.PositionX);
if (tfOutput.Valid)
{
context.AppendLine($"layout (xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}) out gl_PerVertex");
@@ -552,7 +557,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static void DeclareInputAttribute(CodeGenContext context, StructuredProgramInfo info, int attr)
{
string suffix = AttributeInfo.IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: false) ? "[]" : string.Empty;
string suffix = IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: false) ? "[]" : string.Empty;
string iq = string.Empty;
if (context.Config.Stage == ShaderStage.Fragment)
@@ -569,7 +574,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
if (context.Config.TransformFeedbackEnabled && context.Config.Stage == ShaderStage.Fragment)
{
for (int c = 0; c < 4; c++)
int components = context.Config.GetTransformFeedbackOutputComponents(attr, 0);
if (components > 1)
{
string type = components switch
{
2 => "vec2",
3 => "vec3",
4 => "vec4",
_ => "float"
};
context.AppendLine($"layout (location = {attr}) in {type} {name};");
}
for (int c = components > 1 ? components : 0; c < 4; c++)
{
char swzMask = "xyzw"[c];
@@ -636,13 +656,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static void DeclareOutputAttribute(CodeGenContext context, int attr)
{
string suffix = AttributeInfo.IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: true) ? "[]" : string.Empty;
string suffix = IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: true) ? "[]" : string.Empty;
string name = $"{DefaultNames.OAttributePrefix}{attr}{suffix}";
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
{
int attrOffset = AttributeConsts.UserAttributeBase + attr * 16;
int components = context.Config.LastInPipeline ? context.Info.GetTransformFeedbackOutputComponents(attrOffset) : 1;
int components = context.Config.GetTransformFeedbackOutputComponents(attr, 0);
if (components > 1)
{
@@ -656,7 +675,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string xfb = string.Empty;
var tfOutput = context.Info.GetTransformFeedbackOutput(attrOffset);
var tfOutput = context.Config.GetTransformFeedbackOutput(attr, 0);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
@@ -664,22 +683,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine($"layout (location = {attr}{xfb}) out {type} {name};");
}
else
for (int c = components > 1 ? components : 0; c < 4; c++)
{
for (int c = 0; c < 4; c++)
char swzMask = "xyzw"[c];
string xfb = string.Empty;
var tfOutput = context.Config.GetTransformFeedbackOutput(attr, c);
if (tfOutput.Valid)
{
char swzMask = "xyzw"[c];
string xfb = string.Empty;
var tfOutput = context.Info.GetTransformFeedbackOutput(attrOffset + c * 4);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {attr}, component = {c}{xfb}) out float {name}_{swzMask};");
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {attr}, component = {c}{xfb}) out float {name}_{swzMask};");
}
}
else
@@ -712,6 +729,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine($"layout (location = {attr}, index = 1) out vec4 {name2};");
}
private static bool IsArrayAttributeGlsl(ShaderStage stage, bool isOutAttr)
{
if (isOutAttr)
{
return stage == ShaderStage.TessellationControl;
}
else
{
return stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
}
private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
{
foreach (int attr in attrs.Order())

View File

@@ -1,5 +1,4 @@
using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
@@ -126,21 +125,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
else if (node is AstAssignment assignment)
{
AggregateType dstType = OperandManager.GetNodeDestType(context, assignment.Destination);
AggregateType srcType = OperandManager.GetNodeDestType(context, assignment.Source);
AggregateType dstType = OperandManager.GetNodeDestType(context, assignment.Destination, isAsgDest: true);
string dest;
if (assignment.Destination is AstOperand operand && operand.Type.IsAttribute())
{
bool perPatch = operand.Type == OperandType.AttributePerPatch;
dest = OperandManager.GetOutAttributeName(context, operand.Value, perPatch);
}
else
{
dest = InstGen.GetExpression(context, assignment.Destination);
}
string dest = InstGen.GetExpression(context, assignment.Destination);
string src = ReinterpretCast(context, assignment.Source, srcType, dstType);
context.AppendLine(dest + " = " + src + ";");

View File

@@ -73,7 +73,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
// For shared memory access, the second argument is unused and should be ignored.
// It is there to make both storage and shared access have the same number of arguments.
// For storage, both inputs are consumed when the argument index is 0, so we should skip it here.
if (argIndex == 1 && (atomic || (inst & Instruction.MrMask) == Instruction.MrShared))
if (argIndex == 1 && (atomic || operation.StorageKind == StorageKind.SharedMemory))
{
continue;
}
@@ -85,14 +85,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (argIndex == 0 && atomic)
{
Instruction memRegion = inst & Instruction.MrMask;
switch (memRegion)
switch (operation.StorageKind)
{
case Instruction.MrShared: args += LoadShared(context, operation); break;
case Instruction.MrStorage: args += LoadStorage(context, operation); break;
case StorageKind.SharedMemory: args += LoadShared(context, operation); break;
case StorageKind.StorageBuffer: args += LoadStorage(context, operation); break;
default: throw new InvalidOperationException($"Invalid memory region \"{memRegion}\".");
default: throw new InvalidOperationException($"Invalid storage kind \"{operation.StorageKind}\".");
}
}
else
@@ -166,8 +164,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
case Instruction.ImageAtomic:
return ImageLoadOrStore(context, operation);
case Instruction.LoadAttribute:
return LoadAttribute(context, operation);
case Instruction.Load:
return Load(context, operation);
case Instruction.LoadConstant:
return LoadConstant(context, operation);
@@ -193,8 +191,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
case Instruction.PackHalf2x16:
return PackHalf2x16(context, operation);
case Instruction.StoreAttribute:
return StoreAttribute(context, operation);
case Instruction.Store:
return Store(context, operation);
case Instruction.StoreLocal:
return StoreLocal(context, operation);

View File

@@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.ImageStore, InstType.Special);
Add(Instruction.ImageAtomic, InstType.Special);
Add(Instruction.IsNan, InstType.CallUnary, "isnan");
Add(Instruction.LoadAttribute, InstType.Special);
Add(Instruction.Load, InstType.Special);
Add(Instruction.LoadConstant, InstType.Special);
Add(Instruction.LoadLocal, InstType.Special);
Add(Instruction.LoadShared, InstType.Special);
@@ -118,7 +118,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
Add(Instruction.ShuffleXor, InstType.CallQuaternary, HelperFunctionNames.ShuffleXor);
Add(Instruction.Sine, InstType.CallUnary, "sin");
Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt");
Add(Instruction.StoreAttribute, InstType.Special);
Add(Instruction.Store, InstType.Special);
Add(Instruction.StoreLocal, InstType.Special);
Add(Instruction.StoreShared, InstType.Special);
Add(Instruction.StoreShared16, InstType.Special);

View File

@@ -210,30 +210,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return texCallBuilder.ToString();
}
public static string LoadAttribute(CodeGenContext context, AstOperation operation)
public static string Load(CodeGenContext context, AstOperation operation)
{
IAstNode src1 = operation.GetSource(0);
IAstNode src2 = operation.GetSource(1);
IAstNode src3 = operation.GetSource(2);
if (!(src1 is AstOperand baseAttr) || baseAttr.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {nameof(Instruction.LoadAttribute)} must be a constant operand.");
}
string indexExpr = GetSoureExpr(context, src3, GetSrcVarType(operation.Inst, 2));
if (src2 is AstOperand operand && operand.Type == OperandType.Constant)
{
int attrOffset = baseAttr.Value + (operand.Value << 2);
return OperandManager.GetAttributeName(context, attrOffset, perPatch: false, isOutAttr: false, indexExpr);
}
else
{
string attrExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
attrExpr = Enclose(attrExpr, src2, Instruction.ShiftRightS32, isLhs: true);
return OperandManager.GetAttributeName(attrExpr, context.Config, isOutAttr: false, indexExpr);
}
return GenerateLoadOrStore(context, operation, isStore: false);
}
public static string LoadConstant(CodeGenContext context, AstOperation operation)
@@ -337,33 +316,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}";
}
public static string StoreAttribute(CodeGenContext context, AstOperation operation)
public static string Store(CodeGenContext context, AstOperation operation)
{
IAstNode src1 = operation.GetSource(0);
IAstNode src2 = operation.GetSource(1);
IAstNode src3 = operation.GetSource(2);
if (!(src1 is AstOperand baseAttr) || baseAttr.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {nameof(Instruction.StoreAttribute)} must be a constant operand.");
}
string attrName;
if (src2 is AstOperand operand && operand.Type == OperandType.Constant)
{
int attrOffset = baseAttr.Value + (operand.Value << 2);
attrName = OperandManager.GetAttributeName(context, attrOffset, perPatch: false, isOutAttr: true);
}
else
{
string attrExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
attrExpr = Enclose(attrExpr, src2, Instruction.ShiftRightS32, isLhs: true);
attrName = OperandManager.GetAttributeName(attrExpr, context.Config, isOutAttr: true);
}
string value = GetSoureExpr(context, src3, GetSrcVarType(operation.Inst, 2));
return $"{attrName} = {value}";
return GenerateLoadOrStore(context, operation, isStore: true);
}
public static string StoreLocal(CodeGenContext context, AstOperation operation)
@@ -677,7 +632,28 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return vector;
}
Append(ApplyScaling(AssemblePVector(pCount)));
string ApplyBias(string vector)
{
int gatherBiasPrecision = context.Config.GpuAccessor.QueryHostGatherBiasPrecision();
if (isGather && gatherBiasPrecision != 0)
{
// GPU requires texture gather to be slightly offset to match NVIDIA behaviour when point is exactly between two texels.
// Offset by the gather precision divided by 2 to correct for rounding.
if (pCount == 1)
{
vector = $"{vector} + (1.0 / (float(textureSize({samplerName}, 0)) * float({1 << (gatherBiasPrecision + 1)})))";
}
else
{
vector = $"{vector} + (1.0 / (vec{pCount}(textureSize({samplerName}, 0).{"xyz".Substring(0, pCount)}) * float({1 << (gatherBiasPrecision + 1)})))";
}
}
return vector;
}
Append(ApplyBias(ApplyScaling(AssemblePVector(pCount))));
string AssembleDerivativesVector(int count)
{
@@ -826,6 +802,111 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
}
private static string GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
{
StorageKind storageKind = operation.StorageKind;
string varName;
AggregateType varType;
int srcIndex = 0;
switch (storageKind)
{
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
case StorageKind.OutputPerPatch:
if (!(operation.GetSource(srcIndex++) is AstOperand varId) || varId.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
IoVariable ioVariable = (IoVariable)varId.Value;
bool isOutput = storageKind.IsOutput();
bool isPerPatch = storageKind.IsPerPatch();
int location = -1;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (!(operation.GetSource(srcIndex++) is AstOperand vecIndex) || vecIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
location = vecIndex.Value;
if (operation.SourcesCount > srcIndex &&
operation.GetSource(srcIndex) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
srcIndex++;
}
}
(varName, varType) = IoMap.GetGlslVariable(context.Config, ioVariable, location, component, isOutput, isPerPatch);
if (IoMap.IsPerVertexBuiltIn(context.Config.Stage, ioVariable, isOutput))
{
// Since those exist both as input and output on geometry and tessellation shaders,
// we need the gl_in and gl_out prefixes to disambiguate.
if (storageKind == StorageKind.Input)
{
string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32);
varName = $"gl_in[{expr}].{varName}";
}
else if (storageKind == StorageKind.Output)
{
string expr = GetSoureExpr(context, operation.GetSource(srcIndex++), AggregateType.S32);
varName = $"gl_out[{expr}].{varName}";
}
}
int firstSrcIndex = srcIndex;
int inputsCount = isStore ? operation.SourcesCount - 1 : operation.SourcesCount;
for (; srcIndex < inputsCount; srcIndex++)
{
IAstNode src = operation.GetSource(srcIndex);
if ((varType & AggregateType.ElementCountMask) != 0 &&
srcIndex == inputsCount - 1 &&
src is AstOperand elementIndex &&
elementIndex.Type == OperandType.Constant)
{
varName += "." + "xyzw"[elementIndex.Value & 3];
}
else if (srcIndex == firstSrcIndex && context.Config.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
{
// GLSL requires that for tessellation control shader outputs,
// that the index expression must be *exactly* "gl_InvocationID",
// otherwise the compilation fails.
// TODO: Get rid of this and use expression propagation to make sure we generate the correct code from IR.
varName += "[gl_InvocationID]";
}
else
{
varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]";
}
}
break;
default:
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
}
if (isStore)
{
varType &= AggregateType.ElementTypeMask;
varName = $"{varName} = {GetSoureExpr(context, operation.GetSource(srcIndex), varType)}";
}
return varName;
}
private static string GetStorageBufferAccessor(string slotExpr, string offsetExpr, ShaderStage stage)
{
string sbName = OperandManager.GetShaderStagePrefix(stage);

View File

@@ -0,0 +1,145 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using System.Globalization;
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{
static class IoMap
{
public static (string, AggregateType) GetGlslVariable(
ShaderConfig config,
IoVariable ioVariable,
int location,
int component,
bool isOutput,
bool isPerPatch)
{
return ioVariable switch
{
IoVariable.BackColorDiffuse => ("gl_BackColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.BackColorSpecular => ("gl_BackSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.BaseInstance => ("gl_BaseInstanceARB", AggregateType.S32),
IoVariable.BaseVertex => ("gl_BaseVertexARB", AggregateType.S32),
IoVariable.ClipDistance => ("gl_ClipDistance", AggregateType.Array | AggregateType.FP32),
IoVariable.CtaId => ("gl_WorkGroupID", AggregateType.Vector3 | AggregateType.U32),
IoVariable.DrawIndex => ("gl_DrawIDARB", AggregateType.S32),
IoVariable.FogCoord => ("gl_FogFragCoord", AggregateType.FP32), // Deprecated.
IoVariable.FragmentCoord => ("gl_FragCoord", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.FragmentOutputColor => GetFragmentOutputColorVariableName(config, location),
IoVariable.FragmentOutputDepth => ("gl_FragDepth", AggregateType.FP32),
IoVariable.FragmentOutputIsBgra => (DefaultNames.SupportBlockIsBgraName, AggregateType.Array | AggregateType.Bool),
IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.FrontFacing => ("gl_FrontFacing", AggregateType.Bool),
IoVariable.InstanceId => ("gl_InstanceID", AggregateType.S32),
IoVariable.InstanceIndex => ("gl_InstanceIndex", AggregateType.S32),
IoVariable.InvocationId => ("gl_InvocationID", AggregateType.S32),
IoVariable.Layer => ("gl_Layer", AggregateType.S32),
IoVariable.PatchVertices => ("gl_PatchVerticesIn", AggregateType.S32),
IoVariable.PointCoord => ("gl_PointCoord", AggregateType.Vector2 | AggregateType.FP32),
IoVariable.PointSize => ("gl_PointSize", AggregateType.FP32),
IoVariable.Position => ("gl_Position", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.PrimitiveId => GetPrimitiveIdVariableName(config.Stage, isOutput),
IoVariable.SubgroupEqMask => GetSubgroupMaskVariableName(config, "Eq"),
IoVariable.SubgroupGeMask => GetSubgroupMaskVariableName(config, "Ge"),
IoVariable.SubgroupGtMask => GetSubgroupMaskVariableName(config, "Gt"),
IoVariable.SubgroupLaneId => GetSubgroupInvocationIdVariableName(config),
IoVariable.SubgroupLeMask => GetSubgroupMaskVariableName(config, "Le"),
IoVariable.SubgroupLtMask => GetSubgroupMaskVariableName(config, "Lt"),
IoVariable.SupportBlockRenderScale => (DefaultNames.SupportBlockRenderScaleName, AggregateType.Array | AggregateType.FP32),
IoVariable.SupportBlockViewInverse => (DefaultNames.SupportBlockViewportInverse, AggregateType.Vector2 | AggregateType.FP32),
IoVariable.TessellationCoord => ("gl_TessCoord", AggregateType.Vector3 | AggregateType.FP32),
IoVariable.TessellationLevelInner => ("gl_TessLevelInner", AggregateType.Array | AggregateType.FP32),
IoVariable.TessellationLevelOuter => ("gl_TessLevelOuter", AggregateType.Array | AggregateType.FP32),
IoVariable.TextureCoord => ("gl_TexCoord", AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.ThreadId => ("gl_LocalInvocationID", AggregateType.Vector3 | AggregateType.U32),
IoVariable.ThreadKill => ("gl_HelperInvocation", AggregateType.Bool),
IoVariable.UserDefined => GetUserDefinedVariableName(config, location, component, isOutput, isPerPatch),
IoVariable.VertexId => ("gl_VertexID", AggregateType.S32),
IoVariable.VertexIndex => ("gl_VertexIndex", AggregateType.S32),
IoVariable.ViewportIndex => ("gl_ViewportIndex", AggregateType.S32),
IoVariable.ViewportMask => ("gl_ViewportMask", AggregateType.Array | AggregateType.S32),
_ => (null, AggregateType.Invalid)
};
}
public static bool IsPerVertexBuiltIn(ShaderStage stage, IoVariable ioVariable, bool isOutput)
{
switch (ioVariable)
{
case IoVariable.Layer:
case IoVariable.ViewportIndex:
case IoVariable.PointSize:
case IoVariable.Position:
case IoVariable.ClipDistance:
case IoVariable.PointCoord:
case IoVariable.ViewportMask:
if (isOutput)
{
return stage == ShaderStage.TessellationControl;
}
else
{
return stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
}
return false;
}
private static (string, AggregateType) GetFragmentOutputColorVariableName(ShaderConfig config, int location)
{
if (location < 0)
{
return (DefaultNames.OAttributePrefix, config.GetFragmentOutputColorType(0));
}
string name = DefaultNames.OAttributePrefix + location.ToString(CultureInfo.InvariantCulture);
return (name, config.GetFragmentOutputColorType(location));
}
private static (string, AggregateType) GetPrimitiveIdVariableName(ShaderStage stage, bool isOutput)
{
// The geometry stage has an additional gl_PrimitiveIDIn variable.
return (isOutput || stage != ShaderStage.Geometry ? "gl_PrimitiveID" : "gl_PrimitiveIDIn", AggregateType.S32);
}
private static (string, AggregateType) GetSubgroupMaskVariableName(ShaderConfig config, string cc)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
? ($"unpackUint2x32(gl_SubGroup{cc}MaskARB)", AggregateType.Vector2 | AggregateType.U32)
: ($"gl_Subgroup{cc}Mask", AggregateType.Vector4 | AggregateType.U32);
}
private static (string, AggregateType) GetSubgroupInvocationIdVariableName(ShaderConfig config)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
? ("gl_SubGroupInvocationARB", AggregateType.U32)
: ("gl_SubgroupInvocationID", AggregateType.U32);
}
private static (string, AggregateType) GetUserDefinedVariableName(ShaderConfig config, int location, int component, bool isOutput, bool isPerPatch)
{
string name = isPerPatch
? DefaultNames.PerPatchAttributePrefix
: (isOutput ? DefaultNames.OAttributePrefix : DefaultNames.IAttributePrefix);
if (location < 0)
{
return (name, config.GetUserDefinedType(0, isOutput));
}
name += location.ToString(CultureInfo.InvariantCulture);
if (config.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput))
{
name += "_" + "xyzw"[component & 3];
}
return (name, config.GetUserDefinedType(location, isOutput));
}
}
}

View File

@@ -1,10 +1,10 @@
using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
@@ -12,82 +12,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
class OperandManager
{
private static readonly string[] StagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
private readonly struct BuiltInAttribute
{
public string Name { get; }
public AggregateType Type { get; }
public BuiltInAttribute(string name, AggregateType type)
{
Name = name;
Type = type;
}
}
private static Dictionary<int, BuiltInAttribute> _builtInAttributes = new Dictionary<int, BuiltInAttribute>()
{
{ AttributeConsts.Layer, new BuiltInAttribute("gl_Layer", AggregateType.S32) },
{ AttributeConsts.PointSize, new BuiltInAttribute("gl_PointSize", AggregateType.FP32) },
{ AttributeConsts.PositionX, new BuiltInAttribute("gl_Position.x", AggregateType.FP32) },
{ AttributeConsts.PositionY, new BuiltInAttribute("gl_Position.y", AggregateType.FP32) },
{ AttributeConsts.PositionZ, new BuiltInAttribute("gl_Position.z", AggregateType.FP32) },
{ AttributeConsts.PositionW, new BuiltInAttribute("gl_Position.w", AggregateType.FP32) },
{ AttributeConsts.ClipDistance0, new BuiltInAttribute("gl_ClipDistance[0]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance1, new BuiltInAttribute("gl_ClipDistance[1]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance2, new BuiltInAttribute("gl_ClipDistance[2]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance3, new BuiltInAttribute("gl_ClipDistance[3]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance4, new BuiltInAttribute("gl_ClipDistance[4]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance5, new BuiltInAttribute("gl_ClipDistance[5]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance6, new BuiltInAttribute("gl_ClipDistance[6]", AggregateType.FP32) },
{ AttributeConsts.ClipDistance7, new BuiltInAttribute("gl_ClipDistance[7]", AggregateType.FP32) },
{ AttributeConsts.PointCoordX, new BuiltInAttribute("gl_PointCoord.x", AggregateType.FP32) },
{ AttributeConsts.PointCoordY, new BuiltInAttribute("gl_PointCoord.y", AggregateType.FP32) },
{ AttributeConsts.TessCoordX, new BuiltInAttribute("gl_TessCoord.x", AggregateType.FP32) },
{ AttributeConsts.TessCoordY, new BuiltInAttribute("gl_TessCoord.y", AggregateType.FP32) },
{ AttributeConsts.InstanceId, new BuiltInAttribute("gl_InstanceID", AggregateType.S32) },
{ AttributeConsts.VertexId, new BuiltInAttribute("gl_VertexID", AggregateType.S32) },
{ AttributeConsts.BaseInstance, new BuiltInAttribute("gl_BaseInstanceARB", AggregateType.S32) },
{ AttributeConsts.BaseVertex, new BuiltInAttribute("gl_BaseVertexARB", AggregateType.S32) },
{ AttributeConsts.InstanceIndex, new BuiltInAttribute("gl_InstanceIndex", AggregateType.S32) },
{ AttributeConsts.VertexIndex, new BuiltInAttribute("gl_VertexIndex", AggregateType.S32) },
{ AttributeConsts.DrawIndex, new BuiltInAttribute("gl_DrawIDARB", AggregateType.S32) },
{ AttributeConsts.FrontFacing, new BuiltInAttribute("gl_FrontFacing", AggregateType.Bool) },
// Special.
{ AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth", AggregateType.FP32) },
{ AttributeConsts.ThreadKill, new BuiltInAttribute("gl_HelperInvocation", AggregateType.Bool) },
{ AttributeConsts.ThreadIdX, new BuiltInAttribute("gl_LocalInvocationID.x", AggregateType.U32) },
{ AttributeConsts.ThreadIdY, new BuiltInAttribute("gl_LocalInvocationID.y", AggregateType.U32) },
{ AttributeConsts.ThreadIdZ, new BuiltInAttribute("gl_LocalInvocationID.z", AggregateType.U32) },
{ AttributeConsts.CtaIdX, new BuiltInAttribute("gl_WorkGroupID.x", AggregateType.U32) },
{ AttributeConsts.CtaIdY, new BuiltInAttribute("gl_WorkGroupID.y", AggregateType.U32) },
{ AttributeConsts.CtaIdZ, new BuiltInAttribute("gl_WorkGroupID.z", AggregateType.U32) },
{ AttributeConsts.LaneId, new BuiltInAttribute(null, AggregateType.U32) },
{ AttributeConsts.InvocationId, new BuiltInAttribute("gl_InvocationID", AggregateType.S32) },
{ AttributeConsts.PrimitiveId, new BuiltInAttribute("gl_PrimitiveID", AggregateType.S32) },
{ AttributeConsts.PatchVerticesIn, new BuiltInAttribute("gl_PatchVerticesIn", AggregateType.S32) },
{ AttributeConsts.EqMask, new BuiltInAttribute(null, AggregateType.U32) },
{ AttributeConsts.GeMask, new BuiltInAttribute(null, AggregateType.U32) },
{ AttributeConsts.GtMask, new BuiltInAttribute(null, AggregateType.U32) },
{ AttributeConsts.LeMask, new BuiltInAttribute(null, AggregateType.U32) },
{ AttributeConsts.LtMask, new BuiltInAttribute(null, AggregateType.U32) },
// Support uniforms.
{ AttributeConsts.FragmentOutputIsBgraBase + 0, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[0]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 4, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[1]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 8, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[2]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 12, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[3]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 16, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[4]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 20, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[5]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 24, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[6]", AggregateType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", AggregateType.Bool) },
{ AttributeConsts.SupportBlockViewInverseX, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.x", AggregateType.FP32) },
{ AttributeConsts.SupportBlockViewInverseY, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.y", AggregateType.FP32) }
};
private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
private Dictionary<AstOperand, string> _locals;
@@ -110,8 +35,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return operand.Type switch
{
OperandType.Argument => GetArgumentName(operand.Value),
OperandType.Attribute => GetAttributeName(context, operand.Value, perPatch: false),
OperandType.AttributePerPatch => GetAttributeName(context, operand.Value, perPatch: true),
OperandType.Constant => NumberFormatter.FormatInt(operand.Value),
OperandType.ConstantBuffer => GetConstantBufferName(operand, context.Config),
OperandType.LocalVariable => _locals[operand],
@@ -155,177 +78,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return GetVec4Indexed(GetUbName(stage, slotExpr) + $"[{offsetExpr} >> 2]", offsetExpr + " & 3", indexElement);
}
public static string GetOutAttributeName(CodeGenContext context, int value, bool perPatch)
{
return GetAttributeName(context, value, perPatch, isOutAttr: true);
}
public static string GetAttributeName(CodeGenContext context, int value, bool perPatch, bool isOutAttr = false, string indexExpr = "0")
{
ShaderConfig config = context.Config;
if ((value & AttributeConsts.LoadOutputMask) != 0)
{
isOutAttr = true;
}
value &= AttributeConsts.Mask & ~3;
char swzMask = GetSwizzleMask((value >> 2) & 3);
if (perPatch)
{
if (value >= AttributeConsts.UserAttributePerPatchBase && value < AttributeConsts.UserAttributePerPatchEnd)
{
value -= AttributeConsts.UserAttributePerPatchBase;
return $"{DefaultNames.PerPatchAttributePrefix}{(value >> 4)}.{swzMask}";
}
else if (value < AttributeConsts.UserAttributePerPatchBase)
{
return value switch
{
AttributeConsts.TessLevelOuter0 => "gl_TessLevelOuter[0]",
AttributeConsts.TessLevelOuter1 => "gl_TessLevelOuter[1]",
AttributeConsts.TessLevelOuter2 => "gl_TessLevelOuter[2]",
AttributeConsts.TessLevelOuter3 => "gl_TessLevelOuter[3]",
AttributeConsts.TessLevelInner0 => "gl_TessLevelInner[0]",
AttributeConsts.TessLevelInner1 => "gl_TessLevelInner[1]",
_ => null
};
}
}
else if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd)
{
int attrOffset = value;
value -= AttributeConsts.UserAttributeBase;
string prefix = isOutAttr
? DefaultNames.OAttributePrefix
: DefaultNames.IAttributePrefix;
bool indexable = config.UsedFeatures.HasFlag(isOutAttr ? FeatureFlags.OaIndexing : FeatureFlags.IaIndexing);
if (indexable)
{
string name = prefix;
if (config.Stage == ShaderStage.Geometry && !isOutAttr)
{
name += $"[{indexExpr}]";
}
return name + $"[{(value >> 4)}]." + swzMask;
}
else if (config.TransformFeedbackEnabled &&
((config.LastInVertexPipeline && isOutAttr) ||
(config.Stage == ShaderStage.Fragment && !isOutAttr)))
{
int components = config.LastInPipeline ? context.Info.GetTransformFeedbackOutputComponents(attrOffset) : 1;
string name = components > 1 ? $"{prefix}{(value >> 4)}" : $"{prefix}{(value >> 4)}_{swzMask}";
if (AttributeInfo.IsArrayAttributeGlsl(config.Stage, isOutAttr))
{
name += isOutAttr ? "[gl_InvocationID]" : $"[{indexExpr}]";
}
return components > 1 ? name + '.' + swzMask : name;
}
else
{
string name = $"{prefix}{(value >> 4)}";
if (AttributeInfo.IsArrayAttributeGlsl(config.Stage, isOutAttr))
{
name += isOutAttr ? "[gl_InvocationID]" : $"[{indexExpr}]";
}
return name + '.' + swzMask;
}
}
else
{
if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
{
value -= AttributeConsts.FragmentOutputColorBase;
return $"{DefaultNames.OAttributePrefix}{(value >> 4)}.{swzMask}";
}
else if (_builtInAttributes.TryGetValue(value, out BuiltInAttribute builtInAttr))
{
string subgroupMask = value switch
{
AttributeConsts.EqMask => "Eq",
AttributeConsts.GeMask => "Ge",
AttributeConsts.GtMask => "Gt",
AttributeConsts.LeMask => "Le",
AttributeConsts.LtMask => "Lt",
_ => null
};
if (subgroupMask != null)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
? $"unpackUint2x32(gl_SubGroup{subgroupMask}MaskARB).x"
: $"gl_Subgroup{subgroupMask}Mask.x";
}
else if (value == AttributeConsts.LaneId)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
? "gl_SubGroupInvocationARB"
: "gl_SubgroupInvocationID";
}
if (config.Stage == ShaderStage.Fragment)
{
// TODO: There must be a better way to handle this...
switch (value)
{
case AttributeConsts.PositionX: return $"(gl_FragCoord.x / {DefaultNames.SupportBlockRenderScaleName}[0])";
case AttributeConsts.PositionY: return $"(gl_FragCoord.y / {DefaultNames.SupportBlockRenderScaleName}[0])";
case AttributeConsts.PositionZ: return "gl_FragCoord.z";
case AttributeConsts.PositionW: return "gl_FragCoord.w";
case AttributeConsts.FrontFacing:
if (config.GpuAccessor.QueryHostHasFrontFacingBug())
{
// This is required for Intel on Windows, gl_FrontFacing sometimes returns incorrect
// (flipped) values. Doing this seems to fix it.
return "(-floatBitsToInt(float(gl_FrontFacing)) < 0)";
}
break;
}
}
string name = builtInAttr.Name;
if (AttributeInfo.IsArrayAttributeGlsl(config.Stage, isOutAttr) && AttributeInfo.IsArrayBuiltIn(value))
{
name = isOutAttr ? $"gl_out[gl_InvocationID].{name}" : $"gl_in[{indexExpr}].{name}";
}
return name;
}
}
// TODO: Warn about unknown built-in attribute.
return isOutAttr ? "// bad_attr0x" + value.ToString("X") : "0.0";
}
public static string GetAttributeName(string attrExpr, ShaderConfig config, bool isOutAttr = false, string indexExpr = "0")
{
string name = isOutAttr
? DefaultNames.OAttributePrefix
: DefaultNames.IAttributePrefix;
if (config.Stage == ShaderStage.Geometry && !isOutAttr)
{
name += $"[{indexExpr}]";
}
return $"{name}[{attrExpr} >> 2][{attrExpr} & 3]";
}
public static string GetUbName(ShaderStage stage, int slot, bool cbIndexable)
{
if (cbIndexable)
@@ -387,12 +139,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
int index = (int)stage;
if ((uint)index >= StagePrefixes.Length)
if ((uint)index >= _stagePrefixes.Length)
{
return "invalid";
}
return StagePrefixes[index];
return _stagePrefixes[index];
}
private static char GetSwizzleMask(int value)
@@ -405,24 +157,54 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return $"{DefaultNames.ArgumentNamePrefix}{argIndex}";
}
public static AggregateType GetNodeDestType(CodeGenContext context, IAstNode node, bool isAsgDest = false)
public static AggregateType GetNodeDestType(CodeGenContext context, IAstNode node)
{
// TODO: Get rid of that function entirely and return the type from the operation generation
// functions directly, like SPIR-V does.
if (node is AstOperation operation)
{
if (operation.Inst == Instruction.LoadAttribute)
if (operation.Inst == Instruction.Load)
{
// Load attribute basically just returns the attribute value.
// Some built-in attributes may have different types, so we need
// to return the type based on the attribute that is being read.
if (operation.GetSource(0) is AstOperand operand && operand.Type == OperandType.Constant)
switch (operation.StorageKind)
{
if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr))
{
return builtInAttr.Type;
}
}
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
case StorageKind.OutputPerPatch:
if (!(operation.GetSource(0) is AstOperand varId) || varId.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
}
return OperandInfo.GetVarType(OperandType.Attribute);
IoVariable ioVariable = (IoVariable)varId.Value;
bool isOutput = operation.StorageKind == StorageKind.Output || operation.StorageKind == StorageKind.OutputPerPatch;
bool isPerPatch = operation.StorageKind == StorageKind.InputPerPatch || operation.StorageKind == StorageKind.OutputPerPatch;
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (!(operation.GetSource(1) is AstOperand vecIndex) || vecIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
}
location = vecIndex.Value;
if (operation.SourcesCount > 2 &&
operation.GetSource(2) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
}
}
(_, AggregateType varType) = IoMap.GetGlslVariable(context.Config, ioVariable, location, component, isOutput, isPerPatch);
return varType & AggregateType.ElementTypeMask;
}
}
else if (operation.Inst == Instruction.Call)
{
@@ -461,45 +243,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
return context.CurrentFunction.GetArgumentType(argIndex);
}
return GetOperandVarType(context, operand, isAsgDest);
return OperandInfo.GetVarType(operand);
}
else
{
throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\".");
}
}
private static AggregateType GetOperandVarType(CodeGenContext context, AstOperand operand, bool isAsgDest = false)
{
if (operand.Type == OperandType.Attribute)
{
if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr))
{
return builtInAttr.Type;
}
else if (context.Config.Stage == ShaderStage.Vertex && !isAsgDest &&
operand.Value >= AttributeConsts.UserAttributeBase &&
operand.Value < AttributeConsts.UserAttributeEnd)
{
int location = (operand.Value - AttributeConsts.UserAttributeBase) / 16;
AttributeType type = context.Config.GpuAccessor.QueryAttributeType(location);
return type.ToAggregateType();
}
else if (context.Config.Stage == ShaderStage.Fragment && isAsgDest &&
operand.Value >= AttributeConsts.FragmentOutputColorBase &&
operand.Value < AttributeConsts.FragmentOutputColorEnd)
{
int location = (operand.Value - AttributeConsts.FragmentOutputColorBase) / 16;
AttributeType type = context.Config.GpuAccessor.QueryFragmentOutputType(location);
return type.ToAggregateType();
}
}
return OperandInfo.GetVarType(operand);
}
}
}

View File

@@ -29,15 +29,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public Instruction StorageBuffersArray { get; set; }
public Instruction LocalMemory { get; set; }
public Instruction SharedMemory { get; set; }
public Instruction InputsArray { get; set; }
public Instruction OutputsArray { get; set; }
public Dictionary<TextureMeta, SamplerType> SamplersTypes { get; } = new Dictionary<TextureMeta, SamplerType>();
public Dictionary<TextureMeta, (Instruction, Instruction, Instruction)> Samplers { get; } = new Dictionary<TextureMeta, (Instruction, Instruction, Instruction)>();
public Dictionary<TextureMeta, (Instruction, Instruction)> Images { get; } = new Dictionary<TextureMeta, (Instruction, Instruction)>();
public Dictionary<int, Instruction> Inputs { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> Outputs { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> InputsPerPatch { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> OutputsPerPatch { get; } = new Dictionary<int, Instruction>();
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
public Instruction CoordTemp { get; set; }
private readonly Dictionary<AstOperand, Instruction> _locals = new Dictionary<AstOperand, Instruction>();
@@ -163,16 +161,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
mainInterface.AddRange(InputsPerPatch.Values);
mainInterface.AddRange(OutputsPerPatch.Values);
if (InputsArray != null)
{
mainInterface.Add(InputsArray);
}
if (OutputsArray != null)
{
mainInterface.Add(OutputsArray);
}
return mainInterface.ToArray();
}
@@ -228,8 +216,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return operand.Type switch
{
IrOperandType.Argument => GetArgument(type, operand),
IrOperandType.Attribute => GetAttribute(type, operand.Value & AttributeConsts.Mask, (operand.Value & AttributeConsts.LoadOutputMask) != 0),
IrOperandType.AttributePerPatch => GetAttributePerPatch(type, operand.Value & AttributeConsts.Mask, (operand.Value & AttributeConsts.LoadOutputMask) != 0),
IrOperandType.Constant => GetConstant(type, operand),
IrOperandType.ConstantBuffer => GetConstantBuffer(type, operand),
IrOperandType.LocalVariable => GetLocal(type, operand),
@@ -275,239 +261,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
};
}
public Instruction GetAttributeElemPointer(int attr, bool isOutAttr, Instruction index, out AggregateType elemType)
{
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
var attrInfo = AttributeInfo.From(Config, attr, isOutAttr);
int attrOffset = attrInfo.BaseValue;
AggregateType type = attrInfo.Type;
Instruction ioVariable, elemIndex;
Instruction invocationId = null;
if (Config.Stage == ShaderStage.TessellationControl && isOutAttr)
{
invocationId = Load(TypeS32(), Inputs[AttributeConsts.InvocationId]);
}
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
if (isUserAttr &&
((!isOutAttr && Config.UsedFeatures.HasFlag(FeatureFlags.IaIndexing)) ||
(isOutAttr && Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing))))
{
elemType = AggregateType.FP32;
ioVariable = isOutAttr ? OutputsArray : InputsArray;
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
var vecIndex = Constant(TypeU32(), (attr - AttributeConsts.UserAttributeBase) >> 4);
bool isArray = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr);
if (invocationId != null && isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, vecIndex, elemIndex);
}
else if (invocationId != null)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, vecIndex, elemIndex);
}
else if (isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
}
else
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, vecIndex, elemIndex);
}
}
bool isViewportInverse = attr == AttributeConsts.SupportBlockViewInverseX || attr == AttributeConsts.SupportBlockViewInverseY;
if (isViewportInverse)
{
elemType = AggregateType.FP32;
elemIndex = Constant(TypeU32(), (attr - AttributeConsts.SupportBlockViewInverseX) >> 2);
return AccessChain(TypePointer(StorageClass.Uniform, TypeFP32()), SupportBuffer, Constant(TypeU32(), 2), elemIndex);
}
elemType = attrInfo.Type & AggregateType.ElementTypeMask;
if (isUserAttr && Config.TransformFeedbackEnabled &&
((isOutAttr && Config.LastInVertexPipeline) ||
(!isOutAttr && Config.Stage == ShaderStage.Fragment)))
{
attrOffset = attr;
type = elemType;
if (Config.LastInPipeline && isOutAttr)
{
int components = Info.GetTransformFeedbackOutputComponents(attr);
if (components > 1)
{
attrOffset &= ~0xf;
type = components switch
{
2 => AggregateType.Vector2 | AggregateType.FP32,
3 => AggregateType.Vector3 | AggregateType.FP32,
4 => AggregateType.Vector4 | AggregateType.FP32,
_ => AggregateType.FP32
};
attrInfo = new AttributeInfo(attrOffset, (attr - attrOffset) / 4, components, type, false);
}
}
}
ioVariable = isOutAttr ? Outputs[attrOffset] : Inputs[attrOffset];
bool isIndexed = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr) && (!attrInfo.IsBuiltin || AttributeInfo.IsArrayBuiltIn(attr));
if ((type & (AggregateType.Array | AggregateType.ElementCountMask)) == 0)
{
if (invocationId != null)
{
return isIndexed
? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index)
: AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId);
}
else
{
return isIndexed ? AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index) : ioVariable;
}
}
elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
if (invocationId != null && isIndexed)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, elemIndex);
}
else if (invocationId != null)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, elemIndex);
}
else if (isIndexed)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, elemIndex);
}
else
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, elemIndex);
}
}
public Instruction GetAttributeElemPointer(Instruction attrIndex, bool isOutAttr, Instruction index, out AggregateType elemType)
{
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
Instruction invocationId = null;
if (Config.Stage == ShaderStage.TessellationControl && isOutAttr)
{
invocationId = Load(TypeS32(), Inputs[AttributeConsts.InvocationId]);
}
elemType = AggregateType.FP32;
var ioVariable = isOutAttr ? OutputsArray : InputsArray;
var vecIndex = ShiftRightLogical(TypeS32(), attrIndex, Constant(TypeS32(), 2));
var elemIndex = BitwiseAnd(TypeS32(), attrIndex, Constant(TypeS32(), 3));
bool isArray = AttributeInfo.IsArrayAttributeSpirv(Config.Stage, isOutAttr);
if (invocationId != null && isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, index, vecIndex, elemIndex);
}
else if (invocationId != null)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, invocationId, vecIndex, elemIndex);
}
else if (isArray)
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, index, vecIndex, elemIndex);
}
else
{
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, vecIndex, elemIndex);
}
}
public Instruction GetAttribute(AggregateType type, int attr, bool isOutAttr, Instruction index = null)
{
if (!AttributeInfo.Validate(Config, attr, isOutAttr: false))
{
return GetConstant(type, new AstOperand(IrOperandType.Constant, 0));
}
var elemPointer = GetAttributeElemPointer(attr, isOutAttr, index, out var elemType);
var value = Load(GetType(elemType), elemPointer);
if (Config.Stage == ShaderStage.Fragment)
{
if (attr == AttributeConsts.PositionX || attr == AttributeConsts.PositionY)
{
var pointerType = TypePointer(StorageClass.Uniform, TypeFP32());
var fieldIndex = Constant(TypeU32(), 4);
var scaleIndex = Constant(TypeU32(), 0);
var scaleElemPointer = AccessChain(pointerType, SupportBuffer, fieldIndex, scaleIndex);
var scale = Load(TypeFP32(), scaleElemPointer);
value = FDiv(TypeFP32(), value, scale);
}
else if (attr == AttributeConsts.FrontFacing && Config.GpuAccessor.QueryHostHasFrontFacingBug())
{
// Workaround for what appears to be a bug on Intel compiler.
var valueFloat = Select(TypeFP32(), value, Constant(TypeFP32(), 1f), Constant(TypeFP32(), 0f));
var valueAsInt = Bitcast(TypeS32(), valueFloat);
var valueNegated = SNegate(TypeS32(), valueAsInt);
value = SLessThan(TypeBool(), valueNegated, Constant(TypeS32(), 0));
}
}
return BitcastIfNeeded(type, elemType, value);
}
public Instruction GetAttributePerPatchElemPointer(int attr, bool isOutAttr, out AggregateType elemType)
{
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
var attrInfo = AttributeInfo.FromPatch(Config, attr, isOutAttr);
int attrOffset = attrInfo.BaseValue;
Instruction ioVariable = isOutAttr ? OutputsPerPatch[attrOffset] : InputsPerPatch[attrOffset];
elemType = attrInfo.Type & AggregateType.ElementTypeMask;
if ((attrInfo.Type & (AggregateType.Array | AggregateType.ElementCountMask)) == 0)
{
return ioVariable;
}
var elemIndex = Constant(TypeU32(), attrInfo.GetInnermostIndex());
return AccessChain(TypePointer(storageClass, GetType(elemType)), ioVariable, elemIndex);
}
public Instruction GetAttributePerPatch(AggregateType type, int attr, bool isOutAttr)
{
if (!AttributeInfo.ValidatePerPatch(Config, attr, isOutAttr: false))
{
return GetConstant(type, new AstOperand(IrOperandType.Constant, 0));
}
var elemPointer = GetAttributePerPatchElemPointer(attr, isOutAttr, out var elemType);
return BitcastIfNeeded(type, elemType, Load(GetType(elemType), elemPointer));
}
public Instruction GetAttribute(AggregateType type, Instruction attr, bool isOutAttr, Instruction index = null)
{
var elemPointer = GetAttributeElemPointer(attr, isOutAttr, index, out var elemType);
return BitcastIfNeeded(type, elemType, Load(GetType(elemType), elemPointer));
}
public Instruction GetConstant(AggregateType type, AstOperand operand)
{
return type switch

View File

@@ -1,21 +1,19 @@
using Ryujinx.Common;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using Spv.Generator;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using static Spv.Specification;
using SpvInstruction = Spv.Generator.Instruction;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
static class Declarations
{
// At least 16 attributes are guaranteed by the spec.
public const int MaxAttributes = 16;
private static readonly string[] StagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
public static void DeclareParameters(CodeGenContext context, StructuredFunction function)
@@ -59,7 +57,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++)
{
StructuredFunction function = functions[funcIndex];
Instruction[] locals = new Instruction[function.InArguments.Length];
SpvInstruction[] locals = new SpvInstruction[function.InArguments.Length];
for (int i = 0; i < function.InArguments.Length; i++)
{
@@ -105,10 +103,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
DeclareStorageBuffers(context, context.Config.GetStorageBufferDescriptors());
DeclareSamplers(context, context.Config.GetTextureDescriptors());
DeclareImages(context, context.Config.GetImageDescriptors());
DeclareInputAttributes(context, info, perPatch: false);
DeclareOutputAttributes(context, info, perPatch: false);
DeclareInputAttributes(context, info, perPatch: true);
DeclareOutputAttributes(context, info, perPatch: true);
DeclareInputsAndOutputs(context, info);
}
private static void DeclareLocalMemory(CodeGenContext context, int size)
@@ -121,7 +116,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.SharedMemory = DeclareMemory(context, StorageClass.Workgroup, size);
}
private static Instruction DeclareMemory(CodeGenContext context, StorageClass storage, int size)
private static SpvInstruction DeclareMemory(CodeGenContext context, StorageClass storage, int size)
{
var arrayType = context.TypeArray(context.TypeU32(), context.Constant(context.TypeU32(), size));
var pointerType = context.TypePointer(storage, arrayType);
@@ -395,164 +390,104 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
};
}
private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info, bool perPatch)
private static void DeclareInputsAndOutputs(CodeGenContext context, StructuredProgramInfo info)
{
bool iaIndexing = context.Config.UsedFeatures.HasFlag(FeatureFlags.IaIndexing);
if (iaIndexing && !perPatch)
foreach (var ioDefinition in info.IoDefinitions)
{
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
var ioVariable = ioDefinition.IoVariable;
if (context.Config.Stage == ShaderStage.Geometry)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)context.InputVertices));
}
var spvType = context.TypePointer(StorageClass.Input, attrType);
var spvVar = context.Variable(spvType, StorageClass.Input);
if (context.Config.PassthroughAttributes != 0 && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
context.AddGlobalVariable(spvVar);
context.InputsArray = spvVar;
}
var inputs = perPatch ? info.InputsPerPatch : info.Inputs;
foreach (int attr in inputs)
{
if (!AttributeInfo.Validate(context.Config, attr, isOutAttr: false, perPatch))
// Those are actually from constant buffer, rather than being actual inputs or outputs,
// so we must ignore them here as they are declared as part of the support buffer.
// TODO: Delete this after we represent this properly on the IR (as a constant buffer rather than "input").
if (ioVariable == IoVariable.FragmentOutputIsBgra ||
ioVariable == IoVariable.SupportBlockRenderScale ||
ioVariable == IoVariable.SupportBlockViewInverse)
{
continue;
}
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
if (iaIndexing && isUserAttr && !perPatch)
{
continue;
}
bool isOutput = ioDefinition.StorageKind.IsOutput();
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
PixelImap iq = PixelImap.Unused;
if (context.Config.Stage == ShaderStage.Fragment)
{
if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd)
if (ioVariable == IoVariable.UserDefined)
{
iq = context.Config.ImapTypes[(attr - AttributeConsts.UserAttributeBase) / 16].GetFirstUsedType();
iq = context.Config.ImapTypes[ioDefinition.Location].GetFirstUsedType();
}
else
{
AttributeInfo attrInfo = AttributeInfo.From(context.Config, attr, isOutAttr: false);
AggregateType elemType = attrInfo.Type & AggregateType.ElementTypeMask;
(_, AggregateType varType) = IoMap.GetSpirvBuiltIn(ioVariable);
AggregateType elemType = varType & AggregateType.ElementTypeMask;
if (attrInfo.IsBuiltin && (elemType == AggregateType.S32 || elemType == AggregateType.U32))
if (elemType == AggregateType.S32 || elemType == AggregateType.U32)
{
iq = PixelImap.Constant;
}
}
}
DeclareInputOrOutput(context, attr, perPatch, isOutAttr: false, iq);
DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq);
}
}
private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info, bool perPatch)
private static void DeclareInputOrOutput(CodeGenContext context, IoDefinition ioDefinition, bool isOutput, bool isPerPatch, PixelImap iq = PixelImap.Unused)
{
bool oaIndexing = context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing);
IoVariable ioVariable = ioDefinition.IoVariable;
var storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
if (oaIndexing && !perPatch)
bool isBuiltIn;
BuiltIn builtIn = default;
AggregateType varType;
if (ioVariable == IoVariable.UserDefined)
{
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
varType = context.Config.GetUserDefinedType(ioDefinition.Location, isOutput);
isBuiltIn = false;
}
else if (ioVariable == IoVariable.FragmentOutputColor)
{
varType = context.Config.GetFragmentOutputColorType(ioDefinition.Location);
isBuiltIn = false;
}
else
{
(builtIn, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
isBuiltIn = true;
if (context.Config.Stage == ShaderStage.TessellationControl)
if (varType == AggregateType.Invalid)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
throw new InvalidOperationException($"Unknown variable {ioVariable}.");
}
var spvType = context.TypePointer(StorageClass.Output, attrType);
var spvVar = context.Variable(spvType, StorageClass.Output);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
context.AddGlobalVariable(spvVar);
context.OutputsArray = spvVar;
}
var outputs = perPatch ? info.OutputsPerPatch : info.Outputs;
bool hasComponent = context.Config.HasPerLocationInputOrOutputComponent(ioVariable, ioDefinition.Location, ioDefinition.Component, isOutput);
foreach (int attr in outputs)
if (hasComponent)
{
if (!AttributeInfo.Validate(context.Config, attr, isOutAttr: true, perPatch))
varType &= AggregateType.ElementTypeMask;
}
else if (ioVariable == IoVariable.UserDefined && context.Config.HasTransformFeedbackOutputs(isOutput))
{
varType &= AggregateType.ElementTypeMask;
varType |= context.Config.GetTransformFeedbackOutputComponents(ioDefinition.Location, ioDefinition.Component) switch
{
continue;
}
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
if (oaIndexing && isUserAttr && !perPatch)
{
continue;
}
DeclareOutputAttribute(context, attr, perPatch);
2 => AggregateType.Vector2,
3 => AggregateType.Vector3,
4 => AggregateType.Vector4,
_ => AggregateType.Invalid
};
}
if (context.Config.Stage == ShaderStage.Vertex)
{
DeclareOutputAttribute(context, AttributeConsts.PositionX, perPatch: false);
}
}
private static void DeclareOutputAttribute(CodeGenContext context, int attr, bool perPatch)
{
DeclareInputOrOutput(context, attr, perPatch, isOutAttr: true);
}
public static void DeclareInvocationId(CodeGenContext context)
{
DeclareInputOrOutput(context, AttributeConsts.LaneId, perPatch: false, isOutAttr: false);
}
private static void DeclareInputOrOutput(CodeGenContext context, int attr, bool perPatch, bool isOutAttr, PixelImap iq = PixelImap.Unused)
{
bool isUserAttr = attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd;
if (isUserAttr && context.Config.TransformFeedbackEnabled && !perPatch &&
((isOutAttr && context.Config.LastInVertexPipeline) ||
(!isOutAttr && context.Config.Stage == ShaderStage.Fragment)))
{
DeclareTransformFeedbackInputOrOutput(context, attr, isOutAttr, iq);
return;
}
var dict = perPatch
? (isOutAttr ? context.OutputsPerPatch : context.InputsPerPatch)
: (isOutAttr ? context.Outputs : context.Inputs);
var attrInfo = perPatch
? AttributeInfo.FromPatch(context.Config, attr, isOutAttr)
: AttributeInfo.From(context.Config, attr, isOutAttr);
if (dict.ContainsKey(attrInfo.BaseValue))
{
return;
}
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
var attrType = context.GetType(attrInfo.Type, attrInfo.Length);
var spvType = context.GetType(varType, IoMap.GetSpirvBuiltInArrayLength(ioVariable));
bool builtInPassthrough = false;
if (AttributeInfo.IsArrayAttributeSpirv(context.Config.Stage, isOutAttr) && !perPatch && (!attrInfo.IsBuiltin || AttributeInfo.IsArrayBuiltIn(attr)))
if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Config.Stage, isOutput))
{
int arraySize = context.Config.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)arraySize));
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), (LiteralInteger)arraySize));
if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
@@ -560,69 +495,64 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
if (context.Config.Stage == ShaderStage.TessellationControl && isOutAttr && !perPatch)
if (context.Config.Stage == ShaderStage.TessellationControl && isOutput && !isPerPatch)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
}
var spvType = context.TypePointer(storageClass, attrType);
var spvVar = context.Variable(spvType, storageClass);
var spvPointerType = context.TypePointer(storageClass, spvType);
var spvVar = context.Variable(spvPointerType, storageClass);
if (builtInPassthrough)
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
if (attrInfo.IsBuiltin)
if (isBuiltIn)
{
if (perPatch)
if (isPerPatch)
{
context.Decorate(spvVar, Decoration.Patch);
}
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && attr == AttributeConsts.PositionX && context.Config.Stage != ShaderStage.Fragment)
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && ioVariable == IoVariable.Position)
{
context.Decorate(spvVar, Decoration.Invariant);
}
context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)GetBuiltIn(context, attrInfo.BaseValue));
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline && isOutAttr)
{
var tfOutput = context.Info.GetTransformFeedbackOutput(attrInfo.BaseValue);
if (tfOutput.Valid)
{
context.Decorate(spvVar, Decoration.XfbBuffer, (LiteralInteger)tfOutput.Buffer);
context.Decorate(spvVar, Decoration.XfbStride, (LiteralInteger)tfOutput.Stride);
context.Decorate(spvVar, Decoration.Offset, (LiteralInteger)tfOutput.Offset);
}
}
context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)builtIn);
}
else if (perPatch)
else if (isPerPatch)
{
context.Decorate(spvVar, Decoration.Patch);
int location = context.Config.GetPerPatchAttributeLocation((attr - AttributeConsts.UserAttributePerPatchBase) / 16);
if (ioVariable == IoVariable.UserDefined)
{
int location = context.Config.GetPerPatchAttributeLocation(ioDefinition.Location);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
}
}
else if (isUserAttr)
else if (ioVariable == IoVariable.UserDefined)
{
int location = (attr - AttributeConsts.UserAttributeBase) / 16;
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)ioDefinition.Location);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
if (hasComponent)
{
context.Decorate(spvVar, Decoration.Component, (LiteralInteger)ioDefinition.Component);
}
if (!isOutAttr &&
!perPatch &&
(context.Config.PassthroughAttributes & (1 << location)) != 0 &&
if (!isOutput &&
!isPerPatch &&
(context.Config.PassthroughAttributes & (1 << ioDefinition.Location)) != 0 &&
context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
}
else if (attr >= AttributeConsts.FragmentOutputColorBase && attr < AttributeConsts.FragmentOutputColorEnd)
else if (ioVariable == IoVariable.FragmentOutputColor)
{
int location = (attr - AttributeConsts.FragmentOutputColorBase) / 16;
int location = ioDefinition.Location;
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
{
@@ -646,7 +576,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
if (!isOutAttr)
if (!isOutput)
{
switch (iq)
{
@@ -658,143 +588,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
break;
}
}
context.AddGlobalVariable(spvVar);
dict.Add(attrInfo.BaseValue, spvVar);
}
private static void DeclareTransformFeedbackInputOrOutput(CodeGenContext context, int attr, bool isOutAttr, PixelImap iq = PixelImap.Unused)
{
var dict = isOutAttr ? context.Outputs : context.Inputs;
var attrInfo = AttributeInfo.From(context.Config, attr, isOutAttr);
bool hasComponent = true;
int component = (attr >> 2) & 3;
int components = 1;
var type = attrInfo.Type & AggregateType.ElementTypeMask;
if (context.Config.LastInPipeline && isOutAttr)
else if (context.Config.TryGetTransformFeedbackOutput(
ioVariable,
ioDefinition.Location,
ioDefinition.Component,
out var transformFeedbackOutput))
{
components = context.Info.GetTransformFeedbackOutputComponents(attr);
if (components > 1)
{
attr &= ~0xf;
type = components switch
{
2 => AggregateType.Vector2 | AggregateType.FP32,
3 => AggregateType.Vector3 | AggregateType.FP32,
4 => AggregateType.Vector4 | AggregateType.FP32,
_ => AggregateType.FP32
};
hasComponent = false;
}
}
if (dict.ContainsKey(attr))
{
return;
}
var storageClass = isOutAttr ? StorageClass.Output : StorageClass.Input;
var attrType = context.GetType(type, components);
if (AttributeInfo.IsArrayAttributeSpirv(context.Config.Stage, isOutAttr) && (!attrInfo.IsBuiltin || AttributeInfo.IsArrayBuiltIn(attr)))
{
int arraySize = context.Config.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)arraySize));
}
if (context.Config.Stage == ShaderStage.TessellationControl && isOutAttr)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
}
var spvType = context.TypePointer(storageClass, attrType);
var spvVar = context.Variable(spvType, storageClass);
Debug.Assert(attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd);
int location = (attr - AttributeConsts.UserAttributeBase) / 16;
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
if (hasComponent)
{
context.Decorate(spvVar, Decoration.Component, (LiteralInteger)component);
}
if (isOutAttr)
{
var tfOutput = context.Info.GetTransformFeedbackOutput(attr);
if (tfOutput.Valid)
{
context.Decorate(spvVar, Decoration.XfbBuffer, (LiteralInteger)tfOutput.Buffer);
context.Decorate(spvVar, Decoration.XfbStride, (LiteralInteger)tfOutput.Stride);
context.Decorate(spvVar, Decoration.Offset, (LiteralInteger)tfOutput.Offset);
}
}
else
{
if ((context.Config.PassthroughAttributes & (1 << location)) != 0 &&
context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
switch (iq)
{
case PixelImap.Constant:
context.Decorate(spvVar, Decoration.Flat);
break;
case PixelImap.ScreenLinear:
context.Decorate(spvVar, Decoration.NoPerspective);
break;
}
context.Decorate(spvVar, Decoration.XfbBuffer, (LiteralInteger)transformFeedbackOutput.Buffer);
context.Decorate(spvVar, Decoration.XfbStride, (LiteralInteger)transformFeedbackOutput.Stride);
context.Decorate(spvVar, Decoration.Offset, (LiteralInteger)transformFeedbackOutput.Offset);
}
context.AddGlobalVariable(spvVar);
dict.Add(attr, spvVar);
}
private static BuiltIn GetBuiltIn(CodeGenContext context, int attr)
{
return attr switch
{
AttributeConsts.TessLevelOuter0 => BuiltIn.TessLevelOuter,
AttributeConsts.TessLevelInner0 => BuiltIn.TessLevelInner,
AttributeConsts.Layer => BuiltIn.Layer,
AttributeConsts.ViewportIndex => BuiltIn.ViewportIndex,
AttributeConsts.PointSize => BuiltIn.PointSize,
AttributeConsts.PositionX => context.Config.Stage == ShaderStage.Fragment ? BuiltIn.FragCoord : BuiltIn.Position,
AttributeConsts.ClipDistance0 => BuiltIn.ClipDistance,
AttributeConsts.PointCoordX => BuiltIn.PointCoord,
AttributeConsts.TessCoordX => BuiltIn.TessCoord,
AttributeConsts.InstanceId => BuiltIn.InstanceId,
AttributeConsts.VertexId => BuiltIn.VertexId,
AttributeConsts.BaseInstance => BuiltIn.BaseInstance,
AttributeConsts.BaseVertex => BuiltIn.BaseVertex,
AttributeConsts.InstanceIndex => BuiltIn.InstanceIndex,
AttributeConsts.VertexIndex => BuiltIn.VertexIndex,
AttributeConsts.DrawIndex => BuiltIn.DrawIndex,
AttributeConsts.FrontFacing => BuiltIn.FrontFacing,
AttributeConsts.FragmentOutputDepth => BuiltIn.FragDepth,
AttributeConsts.ThreadKill => BuiltIn.HelperInvocation,
AttributeConsts.ThreadIdX => BuiltIn.LocalInvocationId,
AttributeConsts.CtaIdX => BuiltIn.WorkgroupId,
AttributeConsts.LaneId => BuiltIn.SubgroupLocalInvocationId,
AttributeConsts.InvocationId => BuiltIn.InvocationId,
AttributeConsts.PrimitiveId => BuiltIn.PrimitiveId,
AttributeConsts.PatchVerticesIn => BuiltIn.PatchVertices,
AttributeConsts.EqMask => BuiltIn.SubgroupEqMask,
AttributeConsts.GeMask => BuiltIn.SubgroupGeMask,
AttributeConsts.GtMask => BuiltIn.SubgroupGtMask,
AttributeConsts.LeMask => BuiltIn.SubgroupLeMask,
AttributeConsts.LtMask => BuiltIn.SubgroupLtMask,
AttributeConsts.SupportBlockViewInverseX => BuiltIn.Position,
AttributeConsts.SupportBlockViewInverseY => BuiltIn.Position,
_ => throw new ArgumentException($"Invalid attribute number 0x{attr:X}.")
};
var dict = isPerPatch
? (isOutput ? context.OutputsPerPatch : context.InputsPerPatch)
: (isOutput ? context.Outputs : context.Inputs);
dict.Add(ioDefinition, spvVar);
}
private static string GetStagePrefix(ShaderStage stage)

View File

@@ -1,5 +1,4 @@
using Ryujinx.Graphics.Shader.Translation;
using System;
using System;
using static Spv.Specification;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv

View File

@@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using static Spv.Specification;
@@ -96,7 +97,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.ImageLoad, GenerateImageLoad);
Add(Instruction.ImageStore, GenerateImageStore);
Add(Instruction.IsNan, GenerateIsNan);
Add(Instruction.LoadAttribute, GenerateLoadAttribute);
Add(Instruction.Load, GenerateLoad);
Add(Instruction.LoadConstant, GenerateLoadConstant);
Add(Instruction.LoadLocal, GenerateLoadLocal);
Add(Instruction.LoadShared, GenerateLoadShared);
@@ -132,7 +133,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Add(Instruction.ShuffleXor, GenerateShuffleXor);
Add(Instruction.Sine, GenerateSine);
Add(Instruction.SquareRoot, GenerateSquareRoot);
Add(Instruction.StoreAttribute, GenerateStoreAttribute);
Add(Instruction.Store, GenerateStore);
Add(Instruction.StoreLocal, GenerateStoreLocal);
Add(Instruction.StoreShared, GenerateStoreShared);
Add(Instruction.StoreShared16, GenerateStoreShared16);
@@ -861,31 +862,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return new OperationResult(AggregateType.Bool, result);
}
private static OperationResult GenerateLoadAttribute(CodeGenContext context, AstOperation operation)
private static OperationResult GenerateLoad(CodeGenContext context, AstOperation operation)
{
var src1 = operation.GetSource(0);
var src2 = operation.GetSource(1);
var src3 = operation.GetSource(2);
if (!(src1 is AstOperand baseAttr) || baseAttr.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {nameof(Instruction.LoadAttribute)} must be a constant operand.");
}
var index = context.Get(AggregateType.S32, src3);
var resultType = AggregateType.FP32;
if (src2 is AstOperand operand && operand.Type == OperandType.Constant)
{
int attrOffset = (baseAttr.Value & AttributeConsts.Mask) + (operand.Value << 2);
bool isOutAttr = (baseAttr.Value & AttributeConsts.LoadOutputMask) != 0;
return new OperationResult(resultType, context.GetAttribute(resultType, attrOffset, isOutAttr, index));
}
else
{
var attr = context.Get(AggregateType.S32, src2);
return new OperationResult(resultType, context.GetAttribute(resultType, attr, isOutAttr: false, index));
}
return GenerateLoadOrStore(context, operation, isStore: false);
}
private static OperationResult GenerateLoadConstant(CodeGenContext context, AstOperation operation)
@@ -1223,7 +1202,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
var indexNotSegMask = context.BitwiseAnd(context.TypeU32(), index, notSegMask);
var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
@@ -1253,7 +1232,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var notSegMask = context.Not(context.TypeU32(), segMask);
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
@@ -1280,7 +1259,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var segMask = context.BitwiseAnd(context.TypeU32(), context.ShiftRightLogical(context.TypeU32(), mask, const8), const31);
var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var srcThreadId = context.ISub(context.TypeU32(), threadId, index);
@@ -1309,7 +1288,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var notSegMask = context.Not(context.TypeU32(), segMask);
var clampNotSegMask = context.BitwiseAnd(context.TypeU32(), clamp, notSegMask);
var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
var minThreadId = context.BitwiseAnd(context.TypeU32(), threadId, segMask);
var maxThreadId = context.BitwiseOr(context.TypeU32(), minThreadId, clampNotSegMask);
@@ -1335,35 +1314,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return GenerateUnary(context, operation, context.Delegates.GlslSqrt, null);
}
private static OperationResult GenerateStoreAttribute(CodeGenContext context, AstOperation operation)
private static OperationResult GenerateStore(CodeGenContext context, AstOperation operation)
{
var src1 = operation.GetSource(0);
var src2 = operation.GetSource(1);
var src3 = operation.GetSource(2);
if (!(src1 is AstOperand baseAttr) || baseAttr.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {nameof(Instruction.StoreAttribute)} must be a constant operand.");
}
SpvInstruction elemPointer;
AggregateType elemType;
if (src2 is AstOperand operand && operand.Type == OperandType.Constant)
{
int attrOffset = (baseAttr.Value & AttributeConsts.Mask) + (operand.Value << 2);
elemPointer = context.GetAttributeElemPointer(attrOffset, isOutAttr: true, index: null, out elemType);
}
else
{
var attr = context.Get(AggregateType.S32, src2);
elemPointer = context.GetAttributeElemPointer(attr, isOutAttr: true, index: null, out elemType);
}
var value = context.Get(elemType, src3);
context.Store(elemPointer, value);
return OperationResult.Invalid;
return GenerateLoadOrStore(context, operation, isStore: true);
}
private static OperationResult GenerateStoreLocal(CodeGenContext context, AstOperation operation)
@@ -1447,7 +1400,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var three = context.Constant(context.TypeU32(), 3);
var threadId = context.GetAttribute(AggregateType.U32, AttributeConsts.LaneId, false);
var threadId = GetScalarInput(context, IoVariable.SubgroupLaneId);
var shift = context.BitwiseAnd(context.TypeU32(), threadId, three);
shift = context.ShiftLeftLogical(context.TypeU32(), shift, context.Constant(context.TypeU32(), 1));
var lutIdx = context.ShiftRightLogical(context.TypeU32(), mask, shift);
@@ -1556,6 +1509,33 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
SpvInstruction ApplyBias(SpvInstruction vector, SpvInstruction image)
{
int gatherBiasPrecision = context.Config.GpuAccessor.QueryHostGatherBiasPrecision();
if (isGather && gatherBiasPrecision != 0)
{
// GPU requires texture gather to be slightly offset to match NVIDIA behaviour when point is exactly between two texels.
// Offset by the gather precision divided by 2 to correct for rounding.
var sizeType = pCount == 1 ? context.TypeS32() : context.TypeVector(context.TypeS32(), pCount);
var pVectorType = pCount == 1 ? context.TypeFP32() : context.TypeVector(context.TypeFP32(), pCount);
var bias = context.Constant(context.TypeFP32(), (float)(1 << (gatherBiasPrecision + 1)));
var biasVector = context.CompositeConstruct(pVectorType, Enumerable.Repeat(bias, pCount).ToArray());
var one = context.Constant(context.TypeFP32(), 1f);
var oneVector = context.CompositeConstruct(pVectorType, Enumerable.Repeat(one, pCount).ToArray());
var divisor = context.FMul(
pVectorType,
context.ConvertSToF(pVectorType, context.ImageQuerySize(sizeType, image)),
biasVector);
vector = context.FAdd(pVectorType, vector, context.FDiv(pVectorType, oneVector, divisor));
}
return vector;
}
SpvInstruction pCoords = AssemblePVector(pCount);
pCoords = ScalingHelpers.ApplyScaling(context, texOp, pCoords, intCoords, isBindless, isIndexed, isArray, pCount);
@@ -1716,6 +1696,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
image = context.Image(imageType, image);
}
pCoords = ApplyBias(pCoords, image);
var operands = operandsList.ToArray();
SpvInstruction result;
@@ -1952,20 +1934,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var value = context.GetU32(operation.GetSource(2));
SpvInstruction elemPointer;
Instruction mr = operation.Inst & Instruction.MrMask;
if (mr == Instruction.MrStorage)
if (operation.StorageKind == StorageKind.StorageBuffer)
{
elemPointer = GetStorageElemPointer(context, operation);
}
else if (mr == Instruction.MrShared)
else if (operation.StorageKind == StorageKind.SharedMemory)
{
var offset = context.GetU32(operation.GetSource(0));
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Workgroup, context.TypeU32()), context.SharedMemory, offset);
}
else
{
throw new InvalidOperationException($"Invalid storage class \"{mr}\".");
throw new InvalidOperationException($"Invalid storage kind \"{operation.StorageKind}\".");
}
var one = context.Constant(context.TypeU32(), 1);
@@ -1980,20 +1961,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var value1 = context.GetU32(operation.GetSource(3));
SpvInstruction elemPointer;
Instruction mr = operation.Inst & Instruction.MrMask;
if (mr == Instruction.MrStorage)
if (operation.StorageKind == StorageKind.StorageBuffer)
{
elemPointer = GetStorageElemPointer(context, operation);
}
else if (mr == Instruction.MrShared)
else if (operation.StorageKind == StorageKind.SharedMemory)
{
var offset = context.GetU32(operation.GetSource(0));
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Workgroup, context.TypeU32()), context.SharedMemory, offset);
}
else
{
throw new InvalidOperationException($"Invalid storage class \"{mr}\".");
throw new InvalidOperationException($"Invalid storage kind \"{operation.StorageKind}\".");
}
var one = context.Constant(context.TypeU32(), 1);
@@ -2002,6 +1982,163 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return new OperationResult(AggregateType.U32, context.AtomicCompareExchange(context.TypeU32(), elemPointer, one, zero, zero, value1, value0));
}
private static OperationResult GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
{
StorageKind storageKind = operation.StorageKind;
SpvInstruction pointer;
AggregateType varType;
int srcIndex = 0;
switch (storageKind)
{
case StorageKind.Input:
case StorageKind.InputPerPatch:
case StorageKind.Output:
case StorageKind.OutputPerPatch:
if (!(operation.GetSource(srcIndex++) is AstOperand varId) || varId.Type != OperandType.Constant)
{
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
IoVariable ioVariable = (IoVariable)varId.Value;
bool isOutput = storageKind.IsOutput();
bool isPerPatch = storageKind.IsPerPatch();
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (!(operation.GetSource(srcIndex++) is AstOperand vecIndex) || vecIndex.Type != OperandType.Constant)
{
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
}
location = vecIndex.Value;
if (operation.SourcesCount > srcIndex &&
operation.GetSource(srcIndex) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
srcIndex++;
}
}
if (ioVariable == IoVariable.UserDefined)
{
varType = context.Config.GetUserDefinedType(location, isOutput);
}
else if (ioVariable == IoVariable.FragmentOutputColor)
{
varType = context.Config.GetFragmentOutputColorType(location);
}
else if (ioVariable == IoVariable.FragmentOutputIsBgra)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeU32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 1), elemIndex);
varType = AggregateType.U32;
break;
}
else if (ioVariable == IoVariable.SupportBlockRenderScale)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 4), elemIndex);
varType = AggregateType.FP32;
break;
}
else if (ioVariable == IoVariable.SupportBlockViewInverse)
{
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 2), elemIndex);
varType = AggregateType.FP32;
break;
}
else
{
(_, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
}
varType &= AggregateType.ElementTypeMask;
int inputsCount = (isStore ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
var storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
var ioDefinition = new IoDefinition(storageKind, ioVariable, location, component);
var dict = isPerPatch
? (isOutput ? context.OutputsPerPatch : context.InputsPerPatch)
: (isOutput ? context.Outputs : context.Inputs);
SpvInstruction baseObj = dict[ioDefinition];
SpvInstruction e0, e1, e2;
switch (inputsCount)
{
case 0:
pointer = baseObj;
break;
case 1:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0);
break;
case 2:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1);
break;
case 3:
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
e2 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1, e2);
break;
default:
var indexes = new SpvInstruction[inputsCount];
int index = 0;
for (; index < inputsCount; srcIndex++, index++)
{
indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex));
}
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes);
break;
}
break;
default:
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
}
if (isStore)
{
context.Store(pointer, context.Get(varType, operation.GetSource(srcIndex)));
return OperationResult.Invalid;
}
else
{
var result = context.Load(context.GetType(varType), pointer);
return new OperationResult(varType, result);
}
}
private static SpvInstruction GetScalarInput(CodeGenContext context, IoVariable ioVariable)
{
(_, var varType) = IoMap.GetSpirvBuiltIn(ioVariable);
varType &= AggregateType.ElementTypeMask;
var ioDefinition = new IoDefinition(StorageKind.Input, ioVariable);
return context.Load(context.GetType(varType), context.Inputs[ioDefinition]);
}
private static void GenerateStoreSharedSmallInt(CodeGenContext context, AstOperation operation, int bitSize)
{
var offset = context.Get(AggregateType.U32, operation.GetSource(0));

View File

@@ -0,0 +1,86 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using static Spv.Specification;
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
static class IoMap
{
// At least 16 attributes are guaranteed by the spec.
private const int MaxAttributes = 16;
public static (BuiltIn, AggregateType) GetSpirvBuiltIn(IoVariable ioVariable)
{
return ioVariable switch
{
IoVariable.BaseInstance => (BuiltIn.BaseInstance, AggregateType.S32),
IoVariable.BaseVertex => (BuiltIn.BaseVertex, AggregateType.S32),
IoVariable.ClipDistance => (BuiltIn.ClipDistance, AggregateType.Array | AggregateType.FP32),
IoVariable.CtaId => (BuiltIn.WorkgroupId, AggregateType.Vector3 | AggregateType.U32),
IoVariable.DrawIndex => (BuiltIn.DrawIndex, AggregateType.S32),
IoVariable.FragmentCoord => (BuiltIn.FragCoord, AggregateType.Vector4 | AggregateType.FP32),
IoVariable.FragmentOutputDepth => (BuiltIn.FragDepth, AggregateType.FP32),
IoVariable.FrontFacing => (BuiltIn.FrontFacing, AggregateType.Bool),
IoVariable.InstanceId => (BuiltIn.InstanceId, AggregateType.S32),
IoVariable.InstanceIndex => (BuiltIn.InstanceIndex, AggregateType.S32),
IoVariable.InvocationId => (BuiltIn.InvocationId, AggregateType.S32),
IoVariable.Layer => (BuiltIn.Layer, AggregateType.S32),
IoVariable.PatchVertices => (BuiltIn.PatchVertices, AggregateType.S32),
IoVariable.PointCoord => (BuiltIn.PointCoord, AggregateType.Vector2 | AggregateType.FP32),
IoVariable.PointSize => (BuiltIn.PointSize, AggregateType.FP32),
IoVariable.Position => (BuiltIn.Position, AggregateType.Vector4 | AggregateType.FP32),
IoVariable.PrimitiveId => (BuiltIn.PrimitiveId, AggregateType.S32),
IoVariable.SubgroupEqMask => (BuiltIn.SubgroupEqMask, AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupGeMask => (BuiltIn.SubgroupGeMask, AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupGtMask => (BuiltIn.SubgroupGtMask, AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupLaneId => (BuiltIn.SubgroupLocalInvocationId, AggregateType.U32),
IoVariable.SubgroupLeMask => (BuiltIn.SubgroupLeMask, AggregateType.Vector4 | AggregateType.U32),
IoVariable.SubgroupLtMask => (BuiltIn.SubgroupLtMask, AggregateType.Vector4 | AggregateType.U32),
IoVariable.TessellationCoord => (BuiltIn.TessCoord, AggregateType.Vector3 | AggregateType.FP32),
IoVariable.TessellationLevelInner => (BuiltIn.TessLevelInner, AggregateType.Array | AggregateType.FP32),
IoVariable.TessellationLevelOuter => (BuiltIn.TessLevelOuter, AggregateType.Array | AggregateType.FP32),
IoVariable.ThreadId => (BuiltIn.LocalInvocationId, AggregateType.Vector3 | AggregateType.U32),
IoVariable.ThreadKill => (BuiltIn.HelperInvocation, AggregateType.Bool),
IoVariable.VertexId => (BuiltIn.VertexId, AggregateType.S32),
IoVariable.VertexIndex => (BuiltIn.VertexIndex, AggregateType.S32),
IoVariable.ViewportIndex => (BuiltIn.ViewportIndex, AggregateType.S32),
IoVariable.ViewportMask => (BuiltIn.ViewportMaskNV, AggregateType.Array | AggregateType.S32),
_ => (default, AggregateType.Invalid)
};
}
public static int GetSpirvBuiltInArrayLength(IoVariable ioVariable)
{
return ioVariable switch
{
IoVariable.ClipDistance => 8,
IoVariable.TessellationLevelInner => 2,
IoVariable.TessellationLevelOuter => 4,
IoVariable.ViewportMask => 1,
IoVariable.UserDefined => MaxAttributes,
_ => 1
};
}
public static bool IsPerVertex(IoVariable ioVariable, ShaderStage stage, bool isOutput)
{
switch (ioVariable)
{
case IoVariable.Layer:
case IoVariable.ViewportIndex:
case IoVariable.PointSize:
case IoVariable.Position:
case IoVariable.UserDefined:
case IoVariable.ClipDistance:
case IoVariable.PointCoord:
case IoVariable.ViewportMask:
return !isOutput &&
(stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry);
}
return false;
}
}
}

View File

@@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var vectorFloat = context.ConvertSToF(vector2Type, vector);
var vectorScaled = context.VectorTimesScalar(vector2Type, vectorFloat, scaleNegated);
var fragCoordPointer = context.Inputs[AttributeConsts.PositionX];
var fragCoordPointer = context.Inputs[new IoDefinition(StorageKind.Input, IoVariable.FragmentCoord)];
var fragCoord = context.Load(context.TypeVector(context.TypeFP32(), 4), fragCoordPointer);
var fragCoordXY = context.VectorShuffle(vector2Type, fragCoord, fragCoord, 0, 1);

View File

@@ -63,7 +63,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (config.Stage == ShaderStage.Fragment)
{
if (context.Info.Inputs.Contains(AttributeConsts.Layer))
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)))
{
context.AddCapability(Capability.Geometry);
}
@@ -93,13 +93,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddCapability(Capability.DrawParameters);
}
Declarations.DeclareAll(context, info);
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.ViewportMask)))
{
context.AddExtension("SPV_NV_viewport_array2");
context.AddCapability(Capability.ShaderViewportMaskNV);
}
if ((info.HelperFunctionsMask & NeedsInvocationIdMask) != 0)
{
Declarations.DeclareInvocationId(context);
info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.SubgroupLaneId));
}
Declarations.DeclareAll(context, info);
for (int funcIndex = 0; funcIndex < info.Functions.Count; funcIndex++)
{
var function = info.Functions[funcIndex];
@@ -203,7 +209,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
{
// We invert the front face on Vulkan backend, so we need to do that here aswell.
// We invert the front face on Vulkan backend, so we need to do that here as well.
tessCw = !tessCw;
}
@@ -250,7 +256,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
? ExecutionMode.OriginUpperLeft
: ExecutionMode.OriginLowerLeft);
if (context.Outputs.ContainsKey(AttributeConsts.FragmentOutputDepth))
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.FragmentOutputDepth)))
{
context.AddExecutionMode(spvFunc, ExecutionMode.DepthReplacing);
}
@@ -389,21 +395,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var source = context.Get(dest.VarType, assignment.Source);
context.Store(context.GetLocalPointer(dest), source);
}
else if (dest.Type == OperandType.Attribute || dest.Type == OperandType.AttributePerPatch)
{
bool perPatch = dest.Type == OperandType.AttributePerPatch;
if (AttributeInfo.Validate(context.Config, dest.Value, isOutAttr: true, perPatch))
{
AggregateType elemType;
var elemPointer = perPatch
? context.GetAttributePerPatchElemPointer(dest.Value, true, out elemType)
: context.GetAttributeElemPointer(dest.Value, true, null, out elemType);
context.Store(elemPointer, context.Get(elemType, assignment.Source));
}
}
else if (dest.Type == OperandType.Argument)
{
var source = context.Get(dest.VarType, assignment.Source);

View File

@@ -295,10 +295,12 @@ namespace Ryujinx.Graphics.Shader.Decoders
if (isStore)
{
config.SetAllOutputUserAttributes();
config.SetUsedFeature(FeatureFlags.OaIndexing);
}
else
{
config.SetAllInputUserAttributes();
config.SetUsedFeature(FeatureFlags.IaIndexing);
}
}
else
@@ -340,7 +342,8 @@ namespace Ryujinx.Graphics.Shader.Decoders
}
if (!isStore &&
((attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0) ||
(attr == AttributeConsts.FogCoord ||
(attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0) ||
(attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)))
{
config.SetUsedFeature(FeatureFlags.FixedFuncAttr);

View File

@@ -196,6 +196,15 @@ namespace Ryujinx.Graphics.Shader
return false;
}
/// <summary>
/// Queries host's gather operation precision bits for biasing their coordinates. Zero means no bias.
/// </summary>
/// <returns>Bits of gather operation precision to use for coordinate bias</returns>
int QueryHostGatherBiasPrecision()
{
return 0;
}
/// <summary>
/// Queries host about whether to reduce precision to improve performance.
/// </summary>
@@ -296,9 +305,9 @@ namespace Ryujinx.Graphics.Shader
}
/// <summary>
/// Queries host support for writes to Layer from vertex or tessellation shader stages.
/// Queries host support for writes to the layer from vertex or tessellation shader stages.
/// </summary>
/// <returns>True if writes to layer from vertex or tessellation are supported, false otherwise</returns>
/// <returns>True if writes to the layer from vertex or tessellation are supported, false otherwise</returns>
bool QueryHostSupportsLayerVertexTessellation()
{
return true;
@@ -341,10 +350,19 @@ namespace Ryujinx.Graphics.Shader
}
/// <summary>
/// Queries host GPU shader viewport index output support.
/// Queries host support for writes to the viewport index from vertex or tessellation shader stages.
/// </summary>
/// <returns>True if the GPU and driver supports shader viewport index output, false otherwise</returns>
bool QueryHostSupportsViewportIndex()
/// <returns>True if writes to the viewport index from vertex or tessellation are supported, false otherwise</returns>
bool QueryHostSupportsViewportIndexVertexTessellation()
{
return true;
}
/// <summary>
/// Queries host GPU shader viewport mask output support.
/// </summary>
/// <returns>True if the GPU and driver supports shader viewport mask output, false otherwise</returns>
bool QueryHostSupportsViewportMask()
{
return true;
}

View File

@@ -0,0 +1,351 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Instructions
{
static class AttributeMap
{
private enum StagesMask : byte
{
None = 0,
Compute = 1 << (int)ShaderStage.Compute,
Vertex = 1 << (int)ShaderStage.Vertex,
TessellationControl = 1 << (int)ShaderStage.TessellationControl,
TessellationEvaluation = 1 << (int)ShaderStage.TessellationEvaluation,
Geometry = 1 << (int)ShaderStage.Geometry,
Fragment = 1 << (int)ShaderStage.Fragment,
Tessellation = TessellationControl | TessellationEvaluation,
VertexTessellationGeometry = Vertex | Tessellation | Geometry,
TessellationGeometryFragment = Tessellation | Geometry | Fragment,
AllGraphics = Vertex | Tessellation | Geometry | Fragment
}
private struct AttributeEntry
{
public int BaseOffset { get; }
public AggregateType Type { get; }
public IoVariable IoVariable { get; }
public StagesMask InputMask { get; }
public StagesMask OutputMask { get; }
public AttributeEntry(
int baseOffset,
AggregateType type,
IoVariable ioVariable,
StagesMask inputMask,
StagesMask outputMask)
{
BaseOffset = baseOffset;
Type = type;
IoVariable = ioVariable;
InputMask = inputMask;
OutputMask = outputMask;
}
}
private static readonly IReadOnlyDictionary<int, AttributeEntry> _attributes;
private static readonly IReadOnlyDictionary<int, AttributeEntry> _attributesPerPatch;
static AttributeMap()
{
_attributes = CreateMap();
_attributesPerPatch = CreatePerPatchMap();
}
private static IReadOnlyDictionary<int, AttributeEntry> CreateMap()
{
var map = new Dictionary<int, AttributeEntry>();
Add(map, 0x060, AggregateType.S32, IoVariable.PrimitiveId, StagesMask.TessellationGeometryFragment, StagesMask.Geometry);
Add(map, 0x064, AggregateType.S32, IoVariable.Layer, StagesMask.Fragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x068, AggregateType.S32, IoVariable.ViewportIndex, StagesMask.Fragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x06c, AggregateType.FP32, IoVariable.PointSize, StagesMask.None, StagesMask.VertexTessellationGeometry);
Add(map, 0x070, AggregateType.Vector4 | AggregateType.FP32, IoVariable.Position, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x080, AggregateType.Vector4 | AggregateType.FP32, IoVariable.UserDefined, StagesMask.AllGraphics, StagesMask.VertexTessellationGeometry, 32);
Add(map, 0x280, AggregateType.Vector4 | AggregateType.FP32, IoVariable.FrontColorDiffuse, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x290, AggregateType.Vector4 | AggregateType.FP32, IoVariable.FrontColorSpecular, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x2a0, AggregateType.Vector4 | AggregateType.FP32, IoVariable.BackColorDiffuse, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x2b0, AggregateType.Vector4 | AggregateType.FP32, IoVariable.BackColorSpecular, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x2c0, AggregateType.Array | AggregateType.FP32, IoVariable.ClipDistance, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry, 8);
Add(map, 0x2e0, AggregateType.Vector2 | AggregateType.FP32, IoVariable.PointCoord, StagesMask.Fragment, StagesMask.None);
Add(map, 0x2e8, AggregateType.FP32, IoVariable.FogCoord, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x2f0, AggregateType.Vector2 | AggregateType.FP32, IoVariable.TessellationCoord, StagesMask.TessellationEvaluation, StagesMask.None);
Add(map, 0x2f8, AggregateType.S32, IoVariable.InstanceId, StagesMask.Vertex, StagesMask.None);
Add(map, 0x2fc, AggregateType.S32, IoVariable.VertexId, StagesMask.Vertex, StagesMask.None);
Add(map, 0x300, AggregateType.Vector4 | AggregateType.FP32, IoVariable.TextureCoord, StagesMask.TessellationGeometryFragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x3a0, AggregateType.Array | AggregateType.S32, IoVariable.ViewportMask, StagesMask.Fragment, StagesMask.VertexTessellationGeometry);
Add(map, 0x3fc, AggregateType.Bool, IoVariable.FrontFacing, StagesMask.Fragment, StagesMask.None);
return map;
}
private static IReadOnlyDictionary<int, AttributeEntry> CreatePerPatchMap()
{
var map = new Dictionary<int, AttributeEntry>();
Add(map, 0x000, AggregateType.Vector4 | AggregateType.FP32, IoVariable.TessellationLevelOuter, StagesMask.TessellationEvaluation, StagesMask.TessellationControl);
Add(map, 0x010, AggregateType.Vector2 | AggregateType.FP32, IoVariable.TessellationLevelInner, StagesMask.TessellationEvaluation, StagesMask.TessellationControl);
Add(map, 0x018, AggregateType.Vector4 | AggregateType.FP32, IoVariable.UserDefined, StagesMask.TessellationEvaluation, StagesMask.TessellationControl, 31, 0x200);
return map;
}
private static void Add(
Dictionary<int, AttributeEntry> attributes,
int offset,
AggregateType type,
IoVariable ioVariable,
StagesMask inputMask,
StagesMask outputMask,
int count = 1,
int upperBound = 0x400)
{
int baseOffset = offset;
int elementsCount = GetElementCount(type);
for (int index = 0; index < count; index++)
{
for (int elementIndex = 0; elementIndex < elementsCount; elementIndex++)
{
attributes.Add(offset, new AttributeEntry(baseOffset, type, ioVariable, inputMask, outputMask));
offset += 4;
if (offset >= upperBound)
{
return;
}
}
}
}
public static Operand GenerateAttributeLoad(EmitterContext context, Operand primVertex, int offset, bool isOutput, bool isPerPatch)
{
if (!(isPerPatch ? _attributesPerPatch : _attributes).TryGetValue(offset, out AttributeEntry entry))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid.");
return Const(0);
}
StagesMask validUseMask = isOutput ? entry.OutputMask : entry.InputMask;
if (((StagesMask)(1 << (int)context.Config.Stage) & validUseMask) == StagesMask.None)
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.Config.Stage}.");
return Const(0);
}
if (!IsSupportedByHost(context.Config.GpuAccessor, context.Config.Stage, entry.IoVariable))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.Config.Stage}.");
return Const(0);
}
if (HasInvocationId(context.Config.Stage, isOutput) && !isPerPatch)
{
primVertex = context.Load(StorageKind.Input, IoVariable.InvocationId);
}
int innerOffset = offset - entry.BaseOffset;
int innerIndex = innerOffset / 4;
StorageKind storageKind = isPerPatch
? (isOutput ? StorageKind.OutputPerPatch : StorageKind.InputPerPatch)
: (isOutput ? StorageKind.Output : StorageKind.Input);
IoVariable ioVariable = GetIoVariable(context.Config.Stage, in entry);
AggregateType type = GetType(context.Config, isOutput, innerIndex, in entry);
int elementCount = GetElementCount(type);
bool isArray = type.HasFlag(AggregateType.Array);
bool hasArrayIndex = isArray || context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput);
bool hasElementIndex = elementCount > 1;
if (hasArrayIndex && hasElementIndex)
{
int arrayIndex = innerIndex / elementCount;
int elementIndex = innerIndex - (arrayIndex * elementCount);
return primVertex == null || isArray
? context.Load(storageKind, ioVariable, primVertex, Const(arrayIndex), Const(elementIndex))
: context.Load(storageKind, ioVariable, Const(arrayIndex), primVertex, Const(elementIndex));
}
else if (hasArrayIndex || hasElementIndex)
{
return primVertex == null || isArray || !hasArrayIndex
? context.Load(storageKind, ioVariable, primVertex, Const(innerIndex))
: context.Load(storageKind, ioVariable, Const(innerIndex), primVertex);
}
else
{
return context.Load(storageKind, ioVariable, primVertex);
}
}
public static void GenerateAttributeStore(EmitterContext context, int offset, bool isPerPatch, Operand value)
{
if (!(isPerPatch ? _attributesPerPatch : _attributes).TryGetValue(offset, out AttributeEntry entry))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid.");
return;
}
if (((StagesMask)(1 << (int)context.Config.Stage) & entry.OutputMask) == StagesMask.None)
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.Config.Stage}.");
return;
}
if (!IsSupportedByHost(context.Config.GpuAccessor, context.Config.Stage, entry.IoVariable))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.Config.Stage}.");
return;
}
Operand invocationId = null;
if (HasInvocationId(context.Config.Stage, isOutput: true) && !isPerPatch)
{
invocationId = context.Load(StorageKind.Input, IoVariable.InvocationId);
}
int innerOffset = offset - entry.BaseOffset;
int innerIndex = innerOffset / 4;
StorageKind storageKind = isPerPatch ? StorageKind.OutputPerPatch : StorageKind.Output;
IoVariable ioVariable = GetIoVariable(context.Config.Stage, in entry);
AggregateType type = GetType(context.Config, isOutput: true, innerIndex, in entry);
int elementCount = GetElementCount(type);
bool isArray = type.HasFlag(AggregateType.Array);
bool hasArrayIndex = isArray || context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput: true);
bool hasElementIndex = elementCount > 1;
if (hasArrayIndex && hasElementIndex)
{
int arrayIndex = innerIndex / elementCount;
int elementIndex = innerIndex - (arrayIndex * elementCount);
if (invocationId == null || isArray)
{
context.Store(storageKind, ioVariable, invocationId, Const(arrayIndex), Const(elementIndex), value);
}
else
{
context.Store(storageKind, ioVariable, Const(arrayIndex), invocationId, Const(elementIndex), value);
}
}
else if (hasArrayIndex || hasElementIndex)
{
if (invocationId == null || isArray || !hasArrayIndex)
{
context.Store(storageKind, ioVariable, invocationId, Const(innerIndex), value);
}
else
{
context.Store(storageKind, ioVariable, Const(innerIndex), invocationId, value);
}
}
else
{
context.Store(storageKind, ioVariable, invocationId, value);
}
}
private static bool IsSupportedByHost(IGpuAccessor gpuAccessor, ShaderStage stage, IoVariable ioVariable)
{
if (ioVariable == IoVariable.ViewportIndex && stage != ShaderStage.Geometry && stage != ShaderStage.Fragment)
{
return gpuAccessor.QueryHostSupportsViewportIndexVertexTessellation();
}
else if (ioVariable == IoVariable.ViewportMask)
{
return gpuAccessor.QueryHostSupportsViewportMask();
}
return true;
}
public static IoVariable GetIoVariable(ShaderConfig config, int offset, out int location)
{
location = 0;
if (!_attributes.TryGetValue(offset, out AttributeEntry entry))
{
return IoVariable.Invalid;
}
if (((StagesMask)(1 << (int)config.Stage) & entry.OutputMask) == StagesMask.None)
{
return IoVariable.Invalid;
}
if (config.HasPerLocationInputOrOutput(entry.IoVariable, isOutput: true))
{
location = (offset - entry.BaseOffset) / 16;
}
return GetIoVariable(config.Stage, in entry);
}
private static IoVariable GetIoVariable(ShaderStage stage, in AttributeEntry entry)
{
if (entry.IoVariable == IoVariable.Position && stage == ShaderStage.Fragment)
{
return IoVariable.FragmentCoord;
}
return entry.IoVariable;
}
private static AggregateType GetType(ShaderConfig config, bool isOutput, int innerIndex, in AttributeEntry entry)
{
AggregateType type = entry.Type;
if (entry.IoVariable == IoVariable.UserDefined)
{
type = config.GetUserDefinedType(innerIndex / 4, isOutput);
}
else if (entry.IoVariable == IoVariable.FragmentOutputColor)
{
type = config.GetFragmentOutputColorType(innerIndex / 4);
}
return type;
}
public static bool HasPrimitiveVertex(ShaderStage stage, bool isOutput)
{
if (isOutput)
{
return false;
}
return stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
public static bool HasInvocationId(ShaderStage stage, bool isOutput)
{
return isOutput && stage == ShaderStage.TessellationControl;
}
private static int GetElementCount(AggregateType type)
{
return (type & AggregateType.ElementCountMask) switch
{
AggregateType.Vector2 => 2,
AggregateType.Vector3 => 3,
AggregateType.Vector4 => 4,
_ => 1
};
}
}
}

View File

@@ -20,7 +20,16 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
InstAld op = context.GetOp<InstAld>();
Operand primVertex = context.Copy(GetSrcReg(context, op.SrcB));
// Some of those attributes are per invocation,
// so we should ignore any primitive vertex indexing for those.
bool hasPrimitiveVertex = AttributeMap.HasPrimitiveVertex(context.Config.Stage, op.O) && !op.P;
if (!op.Phys)
{
hasPrimitiveVertex &= HasPrimitiveVertex(op.Imm11);
}
Operand primVertex = hasPrimitiveVertex ? context.Copy(GetSrcReg(context, op.SrcB)) : null;
for (int index = 0; index < (int)op.AlSize + 1; index++)
{
@@ -33,12 +42,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.Phys)
{
Operand userAttrOffset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
Operand userAttrIndex = context.ShiftRightU32(userAttrOffset, Const(2));
Operand offset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
Operand vecIndex = context.ShiftRightU32(offset, Const(4));
Operand elemIndex = context.BitwiseAnd(context.ShiftRightU32(offset, Const(2)), Const(3));
context.Copy(Register(rd), context.LoadAttribute(Const(AttributeConsts.UserAttributeBase), userAttrIndex, primVertex));
StorageKind storageKind = op.O ? StorageKind.Output : StorageKind.Input;
context.Config.SetUsedFeature(FeatureFlags.IaIndexing);
context.Copy(Register(rd), context.Load(storageKind, IoVariable.UserDefined, primVertex, vecIndex, elemIndex));
}
else if (op.SrcB == RegisterConsts.RegisterZeroIndex || op.P)
{
@@ -46,14 +56,16 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.FlagAttributeRead(offset);
if (op.O && CanLoadOutput(offset))
bool isOutput = op.O && CanLoadOutput(offset);
if (!op.P && !isOutput && TryConvertIdToIndexForVulkan(context, offset, out Operand value))
{
offset |= AttributeConsts.LoadOutputMask;
context.Copy(Register(rd), value);
}
else
{
context.Copy(Register(rd), AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, op.P));
}
Operand src = op.P ? AttributePerPatch(offset) : CreateInputAttribute(context, offset);
context.Copy(Register(rd), src);
}
else
{
@@ -61,14 +73,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.FlagAttributeRead(offset);
if (op.O && CanLoadOutput(offset))
{
offset |= AttributeConsts.LoadOutputMask;
}
bool isOutput = op.O && CanLoadOutput(offset);
Operand src = Const(offset);
context.Copy(Register(rd), context.LoadAttribute(src, Const(0), primVertex));
context.Copy(Register(rd), AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, false));
}
}
}
@@ -88,12 +95,14 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.Phys)
{
Operand userAttrOffset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
Operand userAttrIndex = context.ShiftRightU32(userAttrOffset, Const(2));
Operand offset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
Operand vecIndex = context.ShiftRightU32(offset, Const(4));
Operand elemIndex = context.BitwiseAnd(context.ShiftRightU32(offset, Const(2)), Const(3));
Operand invocationId = AttributeMap.HasInvocationId(context.Config.Stage, isOutput: true)
? context.Load(StorageKind.Input, IoVariable.InvocationId)
: null;
context.StoreAttribute(Const(AttributeConsts.UserAttributeBase), userAttrIndex, Register(rd));
context.Config.SetUsedFeature(FeatureFlags.OaIndexing);
context.Store(StorageKind.Output, IoVariable.UserDefined, invocationId, vecIndex, elemIndex, Register(rd));
}
else
{
@@ -110,9 +119,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
context.FlagAttributeWritten(offset);
Operand dest = op.P ? AttributePerPatch(offset) : Attribute(offset);
context.Copy(dest, Register(rd));
AttributeMap.GenerateAttributeStore(context, offset, op.P, Register(rd));
}
}
}
@@ -129,13 +136,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.Idx)
{
Operand userAttrOffset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
Operand userAttrIndex = context.ShiftRightU32(userAttrOffset, Const(2));
Operand offset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
Operand vecIndex = context.ShiftRightU32(offset, Const(4));
Operand elemIndex = context.BitwiseAnd(context.ShiftRightU32(offset, Const(2)), Const(3));
res = context.LoadAttribute(Const(AttributeConsts.UserAttributeBase), userAttrIndex, Const(0));
res = context.FPMultiply(res, Attribute(AttributeConsts.PositionW));
context.Config.SetUsedFeature(FeatureFlags.IaIndexing);
res = context.Load(StorageKind.Input, IoVariable.UserDefined, null, vecIndex, elemIndex);
res = context.FPMultiply(res, context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(3)));
}
else
{
@@ -147,9 +153,21 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (context.Config.ImapTypes[index].GetFirstUsedType() == PixelImap.Perspective)
{
res = context.FPMultiply(res, Attribute(AttributeConsts.PositionW));
res = context.FPMultiply(res, context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(3)));
}
}
else if (op.Imm10 == AttributeConsts.PositionX || op.Imm10 == AttributeConsts.PositionY)
{
// FragCoord X/Y must be divided by the render target scale, if resolution scaling is active,
// because the shader code is not expecting scaled values.
res = context.FPDivide(res, context.Load(StorageKind.Input, IoVariable.SupportBlockRenderScale, null, Const(0)));
}
else if (op.Imm10 == AttributeConsts.FrontFacing && context.Config.GpuAccessor.QueryHostHasFrontFacingBug())
{
// gl_FrontFacing sometimes has incorrect (flipped) values depending how it is accessed on Intel GPUs.
// This weird trick makes it behave.
res = context.ICompareLess(context.INegate(context.IConvertS32ToFP32(res)), Const(0));
}
}
if (op.IpaOp == IpaOp.Multiply && !isFixedFunc)
@@ -216,17 +234,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (tempXLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionX), tempXLocal);
context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(0)), tempXLocal);
}
if (tempYLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionY), tempYLocal);
context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(1)), tempYLocal);
}
if (tempZLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionZ), tempZLocal);
context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(2)), tempZLocal);
}
}
else
@@ -241,6 +259,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
}
private static bool HasPrimitiveVertex(int attr)
{
return attr != AttributeConsts.PrimitiveId &&
attr != AttributeConsts.TessCoordX &&
attr != AttributeConsts.TessCoordY;
}
private static bool CanLoadOutput(int attr)
{
return attr != AttributeConsts.TessCoordX && attr != AttributeConsts.TessCoordY;
@@ -252,13 +277,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
// TODO: If two sided rendering is enabled, then this should return
// FrontColor if the fragment is front facing, and back color otherwise.
int index = (attr - AttributeConsts.FrontColorDiffuseR) >> 4;
int userAttrIndex = context.Config.GetFreeUserAttribute(isOutput: false, index);
Operand frontAttr = Attribute(AttributeConsts.UserAttributeBase + userAttrIndex * 16 + (attr & 0xf));
context.Config.SetInputUserAttributeFixedFunc(userAttrIndex);
selectedAttr = frontAttr;
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.Config, attr, isOutput: false));
return true;
}
else if (attr == AttributeConsts.FogCoord)
{
// TODO: We likely need to emulate the fixed-function functionality for FogCoord here.
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.Config, attr, isOutput: false));
return true;
}
else if (attr >= AttributeConsts.BackColorDiffuseR && attr < AttributeConsts.ClipDistance0)
@@ -268,14 +293,19 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
{
selectedAttr = Attribute(FixedFuncToUserAttribute(context.Config, attr, AttributeConsts.TexCoordBase, 4, isOutput: false));
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.Config, attr, isOutput: false));
return true;
}
selectedAttr = Attribute(attr);
selectedAttr = GenerateIpaLoad(context, attr);
return false;
}
private static Operand GenerateIpaLoad(EmitterContext context, int offset)
{
return AttributeMap.GenerateAttributeLoad(context, null, offset, isOutput: false, isPerPatch: false);
}
private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, bool isOutput)
{
bool supportsLayerFromVertexOrTess = config.GpuAccessor.QueryHostSupportsLayerVertexTessellation();
@@ -286,13 +316,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.Layer, 0, isOutput);
config.SetLayerOutputAttribute(attr);
}
else if (attr == AttributeConsts.FogCoord)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FogCoord, fixedStartAttr, isOutput);
}
else if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FrontColorDiffuseR, fixedStartAttr, isOutput);
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FrontColorDiffuseR, fixedStartAttr + 1, isOutput);
}
else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.TexCoordBase, fixedStartAttr + 4, isOutput);
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.TexCoordBase, fixedStartAttr + 5, isOutput);
}
return attr;
@@ -301,11 +335,10 @@ namespace Ryujinx.Graphics.Shader.Instructions
private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, int baseAttr, int baseIndex, bool isOutput)
{
int index = (attr - baseAttr) >> 4;
int userAttrIndex = config.GetFreeUserAttribute(isOutput, index);
int userAttrIndex = config.GetFreeUserAttribute(isOutput, baseIndex + index);
if ((uint)userAttrIndex < Constants.MaxAttributes)
{
userAttrIndex += baseIndex;
attr = AttributeConsts.UserAttributeBase + userAttrIndex * 16 + (attr & 0xf);
if (isOutput)
@@ -317,25 +350,34 @@ namespace Ryujinx.Graphics.Shader.Instructions
config.SetInputUserAttributeFixedFunc(userAttrIndex);
}
}
else
{
config.GpuAccessor.Log($"No enough user attributes for fixed attribute offset 0x{attr:X}.");
}
return attr;
}
private static Operand CreateInputAttribute(EmitterContext context, int attr)
private static bool TryConvertIdToIndexForVulkan(EmitterContext context, int attr, out Operand value)
{
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
{
if (attr == AttributeConsts.InstanceId)
{
return context.ISubtract(Attribute(AttributeConsts.InstanceIndex), Attribute(AttributeConsts.BaseInstance));
value = context.ISubtract(
context.Load(StorageKind.Input, IoVariable.InstanceIndex),
context.Load(StorageKind.Input, IoVariable.BaseInstance));
return true;
}
else if (attr == AttributeConsts.VertexId)
{
return Attribute(AttributeConsts.VertexIndex);
value = context.Load(StorageKind.Input, IoVariable.VertexIndex);
return true;
}
}
return Attribute(attr);
value = null;
return false;
}
}
}

View File

@@ -25,7 +25,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand value = GetSrcReg(context, op.SrcB);
Operand res = EmitAtomicOp(context, Instruction.MrGlobal, op.Op, op.Size, addrLow, addrHigh, value);
Operand res = EmitAtomicOp(context, StorageKind.GlobalMemory, op.Op, op.Size, addrLow, addrHigh, value);
context.Copy(GetDest(op.Dest), res);
}
@@ -50,7 +50,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
_ => AtomSize.U32
};
Operand res = EmitAtomicOp(context, Instruction.MrShared, op.AtomOp, size, offset, Const(0), value);
Operand res = EmitAtomicOp(context, StorageKind.SharedMemory, op.AtomOp, size, offset, Const(0), value);
context.Copy(GetDest(op.Dest), res);
}
@@ -130,7 +130,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
(Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, new Register(op.SrcA, RegisterType.Gpr), op.E, op.Imm20);
EmitAtomicOp(context, Instruction.MrGlobal, (AtomOp)op.RedOp, op.RedSize, addrLow, addrHigh, GetDest(op.SrcB));
EmitAtomicOp(context, StorageKind.GlobalMemory, (AtomOp)op.RedOp, op.RedSize, addrLow, addrHigh, GetDest(op.SrcB));
}
public static void Stg(EmitterContext context)
@@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
private static Operand EmitAtomicOp(
EmitterContext context,
Instruction mr,
StorageKind storageKind,
AtomOp op,
AtomSize type,
Operand addrLow,
@@ -170,7 +170,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.Add:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicAdd(mr, addrLow, addrHigh, value);
res = context.AtomicAdd(storageKind, addrLow, addrHigh, value);
}
else
{
@@ -180,7 +180,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.And:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicAnd(mr, addrLow, addrHigh, value);
res = context.AtomicAnd(storageKind, addrLow, addrHigh, value);
}
else
{
@@ -190,7 +190,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.Xor:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicXor(mr, addrLow, addrHigh, value);
res = context.AtomicXor(storageKind, addrLow, addrHigh, value);
}
else
{
@@ -200,7 +200,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.Or:
if (type == AtomSize.S32 || type == AtomSize.U32)
{
res = context.AtomicOr(mr, addrLow, addrHigh, value);
res = context.AtomicOr(storageKind, addrLow, addrHigh, value);
}
else
{
@@ -210,11 +210,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.Max:
if (type == AtomSize.S32)
{
res = context.AtomicMaxS32(mr, addrLow, addrHigh, value);
res = context.AtomicMaxS32(storageKind, addrLow, addrHigh, value);
}
else if (type == AtomSize.U32)
{
res = context.AtomicMaxU32(mr, addrLow, addrHigh, value);
res = context.AtomicMaxU32(storageKind, addrLow, addrHigh, value);
}
else
{
@@ -224,11 +224,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
case AtomOp.Min:
if (type == AtomSize.S32)
{
res = context.AtomicMinS32(mr, addrLow, addrHigh, value);
res = context.AtomicMinS32(storageKind, addrLow, addrHigh, value);
}
else if (type == AtomSize.U32)
{
res = context.AtomicMinU32(mr, addrLow, addrHigh, value);
res = context.AtomicMinU32(storageKind, addrLow, addrHigh, value);
}
else
{

View File

@@ -76,11 +76,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
switch (op.SReg)
{
case SReg.LaneId:
src = Attribute(AttributeConsts.LaneId);
src = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId);
break;
case SReg.InvocationId:
src = Attribute(AttributeConsts.InvocationId);
src = context.Load(StorageKind.Input, IoVariable.InvocationId);
break;
case SReg.YDirection:
@@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
case SReg.ThreadKill:
src = context.Config.Stage == ShaderStage.Fragment ? Attribute(AttributeConsts.ThreadKill) : Const(0);
src = context.Config.Stage == ShaderStage.Fragment ? context.Load(StorageKind.Input, IoVariable.ThreadKill) : Const(0);
break;
case SReg.InvocationInfo:
@@ -101,7 +101,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (context.Config.Stage == ShaderStage.TessellationControl ||
context.Config.Stage == ShaderStage.TessellationEvaluation)
{
src = context.ShiftLeft(Attribute(AttributeConsts.PatchVerticesIn), Const(16));
src = context.ShiftLeft(context.Load(StorageKind.Input, IoVariable.PatchVertices), Const(16));
}
else
{
@@ -115,9 +115,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
case SReg.TId:
Operand tidX = Attribute(AttributeConsts.ThreadIdX);
Operand tidY = Attribute(AttributeConsts.ThreadIdY);
Operand tidZ = Attribute(AttributeConsts.ThreadIdZ);
Operand tidX = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(0));
Operand tidY = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(1));
Operand tidZ = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(2));
tidY = context.ShiftLeft(tidY, Const(16));
tidZ = context.ShiftLeft(tidZ, Const(26));
@@ -126,39 +126,39 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
case SReg.TIdX:
src = Attribute(AttributeConsts.ThreadIdX);
src = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(0));
break;
case SReg.TIdY:
src = Attribute(AttributeConsts.ThreadIdY);
src = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(1));
break;
case SReg.TIdZ:
src = Attribute(AttributeConsts.ThreadIdZ);
src = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(2));
break;
case SReg.CtaIdX:
src = Attribute(AttributeConsts.CtaIdX);
src = context.Load(StorageKind.Input, IoVariable.CtaId, null, Const(0));
break;
case SReg.CtaIdY:
src = Attribute(AttributeConsts.CtaIdY);
src = context.Load(StorageKind.Input, IoVariable.CtaId, null, Const(1));
break;
case SReg.CtaIdZ:
src = Attribute(AttributeConsts.CtaIdZ);
src = context.Load(StorageKind.Input, IoVariable.CtaId, null, Const(2));
break;
case SReg.EqMask:
src = Attribute(AttributeConsts.EqMask);
src = context.Load(StorageKind.Input, IoVariable.SubgroupEqMask, null, Const(0));
break;
case SReg.LtMask:
src = Attribute(AttributeConsts.LtMask);
src = context.Load(StorageKind.Input, IoVariable.SubgroupLtMask, null, Const(0));
break;
case SReg.LeMask:
src = Attribute(AttributeConsts.LeMask);
src = context.Load(StorageKind.Input, IoVariable.SubgroupLeMask, null, Const(0));
break;
case SReg.GtMask:
src = Attribute(AttributeConsts.GtMask);
src = context.Load(StorageKind.Input, IoVariable.SubgroupGtMask, null, Const(0));
break;
case SReg.GeMask:
src = Attribute(AttributeConsts.GeMask);
src = context.Load(StorageKind.Input, IoVariable.SubgroupGeMask, null, Const(0));
break;
default:

View File

@@ -78,7 +78,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
ImageStore,
ImageAtomic,
IsNan,
LoadAttribute,
Load,
LoadConstant,
LoadGlobal,
LoadLocal,
@@ -116,7 +116,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
ShuffleXor,
Sine,
SquareRoot,
StoreAttribute,
Store,
StoreGlobal,
StoreGlobal16,
StoreGlobal8,
@@ -144,13 +144,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
FP32 = 1 << 16,
FP64 = 1 << 17,
MrShift = 18,
MrGlobal = 0 << MrShift,
MrShared = 1 << MrShift,
MrStorage = 2 << MrShift,
MrMask = 3 << MrShift,
Mask = 0xffff
}

View File

@@ -0,0 +1,51 @@
namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
{
enum IoVariable
{
Invalid,
BackColorDiffuse,
BackColorSpecular,
BaseInstance,
BaseVertex,
ClipDistance,
CtaId,
DrawIndex,
FogCoord,
FragmentCoord,
FragmentOutputColor,
FragmentOutputDepth,
FragmentOutputIsBgra, // TODO: Remove and use constant buffer access.
FrontColorDiffuse,
FrontColorSpecular,
FrontFacing,
InstanceId,
InstanceIndex,
InvocationId,
Layer,
PatchVertices,
PointCoord,
PointSize,
Position,
PrimitiveId,
SubgroupEqMask,
SubgroupGeMask,
SubgroupGtMask,
SubgroupLaneId,
SubgroupLeMask,
SubgroupLtMask,
SupportBlockViewInverse, // TODO: Remove and use constant buffer access.
SupportBlockRenderScale, // TODO: Remove and use constant buffer access.
TessellationCoord,
TessellationLevelInner,
TessellationLevelOuter,
TextureCoord,
ThreadId,
ThreadKill,
UserDefined,
VertexId,
VertexIndex,
ViewportIndex,
ViewportMask
}
}

View File

@@ -10,16 +10,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
return new Operand(OperandType.Argument, value);
}
public static Operand Attribute(int value)
{
return new Operand(OperandType.Attribute, value);
}
public static Operand AttributePerPatch(int value)
{
return new Operand(OperandType.AttributePerPatch, value);
}
public static Operand Cbuf(int slot, int offset)
{
return new Operand(slot, offset);

View File

@@ -3,8 +3,6 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
enum OperandType
{
Argument,
Attribute,
AttributePerPatch,
Constant,
ConstantBuffer,
Label,
@@ -12,12 +10,4 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Register,
Undefined
}
static class OperandTypeExtensions
{
public static bool IsAttribute(this OperandType type)
{
return type == OperandType.Attribute || type == OperandType.AttributePerPatch;
}
}
}

View File

@@ -6,6 +6,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
class Operation : INode
{
public Instruction Inst { get; private set; }
public StorageKind StorageKind { get; }
private Operand[] _dests;
@@ -99,6 +100,23 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
}
}
public Operation(Instruction inst, StorageKind storageKind, Operand dest, params Operand[] sources) : this(sources)
{
Inst = inst;
StorageKind = storageKind;
if (dest != null)
{
dest.AsgOp = this;
_dests = new[] { dest };
}
else
{
_dests = Array.Empty<Operand>();
}
}
public Operation(Instruction inst, int index, Operand dest, params Operand[] sources) : this(inst, dest, sources)
{
Index = index;

View File

@@ -0,0 +1,39 @@
namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
{
enum StorageKind
{
None,
Input,
InputPerPatch,
Output,
OutputPerPatch,
ConstantBuffer,
StorageBuffer,
LocalMemory,
SharedMemory,
GlobalMemory
}
static class StorageKindExtensions
{
public static bool IsInputOrOutput(this StorageKind storageKind)
{
return storageKind == StorageKind.Input ||
storageKind == StorageKind.InputPerPatch ||
storageKind == StorageKind.Output ||
storageKind == StorageKind.OutputPerPatch;
}
public static bool IsOutput(this StorageKind storageKind)
{
return storageKind == StorageKind.Output ||
storageKind == StorageKind.OutputPerPatch;
}
public static bool IsPerPatch(this StorageKind storageKind)
{
return storageKind == StorageKind.InputPerPatch ||
storageKind == StorageKind.OutputPerPatch;
}
}
}

View File

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

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
int cbufSlot,
int handle,
int index,
params IAstNode[] sources) : base(inst, index, sources, sources.Length)
params IAstNode[] sources) : base(inst, StorageKind.None, index, sources, sources.Length)
{
Type = type;
Format = format;

View File

@@ -89,7 +89,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Add(Instruction.ImageStore, AggregateType.Void);
Add(Instruction.ImageAtomic, AggregateType.S32);
Add(Instruction.IsNan, AggregateType.Bool, AggregateType.Scalar);
Add(Instruction.LoadAttribute, AggregateType.FP32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
Add(Instruction.Load, AggregateType.FP32);
Add(Instruction.LoadConstant, AggregateType.FP32, AggregateType.S32, AggregateType.S32);
Add(Instruction.LoadGlobal, AggregateType.U32, AggregateType.S32, AggregateType.S32);
Add(Instruction.LoadLocal, AggregateType.U32, AggregateType.S32);
@@ -122,7 +122,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Add(Instruction.ShuffleXor, AggregateType.FP32, AggregateType.FP32, AggregateType.U32, AggregateType.U32, AggregateType.Bool);
Add(Instruction.Sine, AggregateType.Scalar, AggregateType.Scalar);
Add(Instruction.SquareRoot, AggregateType.Scalar, AggregateType.Scalar);
Add(Instruction.StoreAttribute, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.FP32);
Add(Instruction.Store, AggregateType.Void);
Add(Instruction.StoreGlobal, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.U32);
Add(Instruction.StoreLocal, AggregateType.Void, AggregateType.S32, AggregateType.U32);
Add(Instruction.StoreShared, AggregateType.Void, AggregateType.S32, AggregateType.U32);
@@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
return AggregateType.FP32;
}
else if (inst == Instruction.Call)
else if (inst == Instruction.Call || inst == Instruction.Load || inst == Instruction.Store)
{
return AggregateType.S32;
}

View File

@@ -0,0 +1,44 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
readonly struct IoDefinition : IEquatable<IoDefinition>
{
public StorageKind StorageKind { get; }
public IoVariable IoVariable { get; }
public int Location { get; }
public int Component { get; }
public IoDefinition(StorageKind storageKind, IoVariable ioVariable, int location = 0, int component = 0)
{
StorageKind = storageKind;
IoVariable = ioVariable;
Location = location;
Component = component;
}
public override bool Equals(object other)
{
return other is IoDefinition ioDefinition && Equals(ioDefinition);
}
public bool Equals(IoDefinition other)
{
return StorageKind == other.StorageKind &&
IoVariable == other.IoVariable &&
Location == other.Location &&
Component == other.Component;
}
public override int GetHashCode()
{
return (int)StorageKind | ((int)IoVariable << 8) | (Location << 16) | (Component << 24);
}
public override string ToString()
{
return $"{StorageKind}.{IoVariable}.{Location}.{Component}";
}
}
}

View File

@@ -23,8 +23,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
return type switch
{
OperandType.Argument => AggregateType.S32,
OperandType.Attribute => AggregateType.FP32,
OperandType.AttributePerPatch => AggregateType.FP32,
OperandType.Constant => AggregateType.S32,
OperandType.ConstantBuffer => AggregateType.FP32,
OperandType.Undefined => AggregateType.S32,

View File

@@ -65,49 +65,35 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
context.LeaveFunction();
}
if (config.TransformFeedbackEnabled && config.LastInVertexPipeline)
{
for (int tfbIndex = 0; tfbIndex < 4; tfbIndex++)
{
var locations = config.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = config.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
for (int i = 0; i < locations.Length; i++)
{
byte location = locations[i];
if (location < 0xc0)
{
context.Info.TransformFeedbackOutputs[location] = new TransformFeedbackOutput(tfbIndex, i * 4, stride);
}
}
}
}
return context.Info;
}
private static void AddOperation(StructuredProgramContext context, Operation operation)
{
Instruction inst = operation.Inst;
StorageKind storageKind = operation.StorageKind;
if (inst == Instruction.LoadAttribute)
if ((inst == Instruction.Load || inst == Instruction.Store) && storageKind.IsInputOrOutput())
{
Operand src1 = operation.GetSource(0);
Operand src2 = operation.GetSource(1);
IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value;
bool isOutput = storageKind.IsOutput();
bool perPatch = storageKind.IsPerPatch();
int location = 0;
int component = 0;
if (src1.Type == OperandType.Constant && src2.Type == OperandType.Constant)
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
int attrOffset = (src1.Value & AttributeConsts.Mask) + (src2.Value << 2);
location = operation.GetSource(1).Value;
if ((src1.Value & AttributeConsts.LoadOutputMask) != 0)
if (operation.SourcesCount > 2 &&
operation.GetSource(2).Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, operation.GetSource(2).Value, isOutput))
{
context.Info.Outputs.Add(attrOffset);
}
else
{
context.Info.Inputs.Add(attrOffset);
component = operation.GetSource(2).Value;
}
}
context.Info.IoDefinitions.Add(new IoDefinition(storageKind, ioVariable, location, component));
}
bool vectorDest = IsVectorDestInst(inst);
@@ -119,12 +105,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
for (int index = 0; index < operation.SourcesCount; index++)
{
sources[index] = context.GetOperandUse(operation.GetSource(index));
sources[index] = context.GetOperand(operation.GetSource(index));
}
for (int index = 0; index < outDestsCount; index++)
{
AstOperand oper = context.GetOperandDef(operation.GetDest(1 + index));
AstOperand oper = context.GetOperand(operation.GetDest(1 + index));
oper.VarType = InstructionInfo.GetSrcVarType(inst, sourcesCount + index);
@@ -163,7 +149,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else
{
source = new AstOperation(inst, operation.Index, sources, operation.SourcesCount);
source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount);
}
AggregateType destElemType = destType;
@@ -181,17 +167,17 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
for (int i = 0; i < operation.DestsCount; i++)
{
AstOperand dest = context.GetOperandDef(operation.GetDest(i));
AstOperand dest = context.GetOperand(operation.GetDest(i));
AstOperand index = new AstOperand(OperandType.Constant, i);
dest.VarType = destElemType;
context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, new[] { destVec, index }, 2)));
context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, StorageKind.None, new[] { destVec, index }, 2)));
}
}
else if (operation.Dest != null)
{
AstOperand dest = context.GetOperandDef(operation.Dest);
AstOperand dest = context.GetOperand(operation.Dest);
// If all the sources are bool, it's better to use short-circuiting
// logical operations, rather than forcing a cast to int and doing
@@ -234,7 +220,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else if (!isCopy)
{
source = new AstOperation(inst, operation.Index, sources, operation.SourcesCount);
source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount);
}
else
{
@@ -255,7 +241,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else
{
context.AddNode(new AstOperation(inst, operation.Index, sources, operation.SourcesCount));
context.AddNode(new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount));
}
// Those instructions needs to be emulated by using helper functions,
@@ -263,13 +249,16 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
// decide which helper functions are needed on the final generated code.
switch (operation.Inst)
{
case Instruction.AtomicMaxS32 | Instruction.MrShared:
case Instruction.AtomicMinS32 | Instruction.MrShared:
context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Shared;
break;
case Instruction.AtomicMaxS32 | Instruction.MrStorage:
case Instruction.AtomicMinS32 | Instruction.MrStorage:
context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Storage;
case Instruction.AtomicMaxS32:
case Instruction.AtomicMinS32:
if (operation.StorageKind == StorageKind.SharedMemory)
{
context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Shared;
}
else if (operation.StorageKind == StorageKind.StorageBuffer)
{
context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Storage;
}
break;
case Instruction.MultiplyHighS32:
context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighS32;

View File

@@ -37,43 +37,26 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Config = config;
if (config.Stage == ShaderStage.TessellationControl)
{
// Required to index outputs.
Info.Inputs.Add(AttributeConsts.InvocationId);
}
else if (config.GpPassthrough)
if (config.GpPassthrough)
{
int passthroughAttributes = config.PassthroughAttributes;
while (passthroughAttributes != 0)
{
int index = BitOperations.TrailingZeroCount(passthroughAttributes);
int attrBase = AttributeConsts.UserAttributeBase + index * 16;
Info.Inputs.Add(attrBase);
Info.Inputs.Add(attrBase + 4);
Info.Inputs.Add(attrBase + 8);
Info.Inputs.Add(attrBase + 12);
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.UserDefined, index));
passthroughAttributes &= ~(1 << index);
}
Info.Inputs.Add(AttributeConsts.PositionX);
Info.Inputs.Add(AttributeConsts.PositionY);
Info.Inputs.Add(AttributeConsts.PositionZ);
Info.Inputs.Add(AttributeConsts.PositionW);
Info.Inputs.Add(AttributeConsts.PointSize);
for (int i = 0; i < 8; i++)
{
Info.Inputs.Add(AttributeConsts.ClipDistance0 + i * 4);
}
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.Position));
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.PointSize));
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.ClipDistance));
}
else if (config.Stage == ShaderStage.Fragment)
{
// Potentially used for texture coordinate scaling.
Info.Inputs.Add(AttributeConsts.PositionX);
Info.Inputs.Add(AttributeConsts.PositionY);
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.FragmentCoord));
}
}
@@ -281,7 +264,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else
{
cond = GetOperandUse(branchOp.GetSource(0));
cond = GetOperand(branchOp.GetSource(0));
Instruction invInst = type == AstBlockType.If
? Instruction.BranchIfTrue
@@ -315,41 +298,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
return newTemp;
}
public AstOperand GetOperandDef(Operand operand)
{
if (operand.Type == OperandType.Attribute)
{
Info.Outputs.Add(operand.Value & AttributeConsts.Mask);
}
else if (operand.Type == OperandType.AttributePerPatch)
{
Info.OutputsPerPatch.Add(operand.Value & AttributeConsts.Mask);
}
return GetOperand(operand);
}
public AstOperand GetOperandUse(Operand operand)
{
// If this flag is set, we're reading from an output attribute instead.
if (operand.Type.IsAttribute() && (operand.Value & AttributeConsts.LoadOutputMask) != 0)
{
return GetOperandDef(operand);
}
if (operand.Type == OperandType.Attribute)
{
Info.Inputs.Add(operand.Value);
}
else if (operand.Type == OperandType.AttributePerPatch)
{
Info.InputsPerPatch.Add(operand.Value);
}
return GetOperand(operand);
}
private AstOperand GetOperand(Operand operand)
public AstOperand GetOperand(Operand operand)
{
if (operand == null)
{

View File

@@ -22,60 +22,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
public List<StructuredFunction> Functions { get; }
public HashSet<int> Inputs { get; }
public HashSet<int> Outputs { get; }
public HashSet<int> InputsPerPatch { get; }
public HashSet<int> OutputsPerPatch { get; }
public HashSet<IoDefinition> IoDefinitions { get; }
public HelperFunctionsMask HelperFunctionsMask { get; set; }
public TransformFeedbackOutput[] TransformFeedbackOutputs { get; }
public StructuredProgramInfo()
{
Functions = new List<StructuredFunction>();
Inputs = new HashSet<int>();
Outputs = new HashSet<int>();
InputsPerPatch = new HashSet<int>();
OutputsPerPatch = new HashSet<int>();
TransformFeedbackOutputs = new TransformFeedbackOutput[0xc0];
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int attr)
{
int index = attr / 4;
return TransformFeedbackOutputs[index];
}
public int GetTransformFeedbackOutputComponents(int attr)
{
int index = attr / 4;
int baseIndex = index & ~3;
int count = 1;
for (; count < 4; count++)
{
ref var prev = ref TransformFeedbackOutputs[baseIndex + count - 1];
ref var curr = ref TransformFeedbackOutputs[baseIndex + count];
int prevOffset = prev.Offset;
int currOffset = curr.Offset;
if (!prev.Valid || !curr.Valid || prevOffset + 4 != currOffset)
{
break;
}
}
if (baseIndex + count <= index)
{
return 1;
}
return count;
IoDefinitions = new HashSet<IoDefinition>();
}
}
}

View File

@@ -2,104 +2,35 @@ namespace Ryujinx.Graphics.Shader.Translation
{
static class AttributeConsts
{
public const int TessLevelOuter0 = 0x000;
public const int TessLevelOuter1 = 0x004;
public const int TessLevelOuter2 = 0x008;
public const int TessLevelOuter3 = 0x00c;
public const int TessLevelInner0 = 0x010;
public const int TessLevelInner1 = 0x014;
public const int PrimitiveId = 0x060;
public const int Layer = 0x064;
public const int ViewportIndex = 0x068;
public const int PointSize = 0x06c;
public const int PositionX = 0x070;
public const int PositionY = 0x074;
public const int PositionZ = 0x078;
public const int PositionW = 0x07c;
public const int FrontColorDiffuseR = 0x280;
public const int FrontColorDiffuseG = 0x284;
public const int FrontColorDiffuseB = 0x288;
public const int FrontColorDiffuseA = 0x28c;
public const int FrontColorSpecularR = 0x290;
public const int FrontColorSpecularG = 0x294;
public const int FrontColorSpecularB = 0x298;
public const int FrontColorSpecularA = 0x29c;
public const int BackColorDiffuseR = 0x2a0;
public const int BackColorDiffuseG = 0x2a4;
public const int BackColorDiffuseB = 0x2a8;
public const int BackColorDiffuseA = 0x2ac;
public const int BackColorSpecularR = 0x2b0;
public const int BackColorSpecularG = 0x2b4;
public const int BackColorSpecularB = 0x2b8;
public const int BackColorSpecularA = 0x2bc;
public const int ClipDistance0 = 0x2c0;
public const int ClipDistance1 = 0x2c4;
public const int ClipDistance2 = 0x2c8;
public const int ClipDistance3 = 0x2cc;
public const int ClipDistance4 = 0x2d0;
public const int ClipDistance5 = 0x2d4;
public const int ClipDistance6 = 0x2d8;
public const int ClipDistance7 = 0x2dc;
public const int PointCoordX = 0x2e0;
public const int PointCoordY = 0x2e4;
public const int TessCoordX = 0x2f0;
public const int TessCoordY = 0x2f4;
public const int InstanceId = 0x2f8;
public const int VertexId = 0x2fc;
public const int TexCoordCount = 10;
public const int TexCoordBase = 0x300;
public const int TexCoordEnd = TexCoordBase + TexCoordCount * 16;
public const int FrontFacing = 0x3fc;
public const int PrimitiveId = 0x060;
public const int Layer = 0x064;
public const int PositionX = 0x070;
public const int PositionY = 0x074;
public const int FrontColorDiffuseR = 0x280;
public const int BackColorDiffuseR = 0x2a0;
public const int ClipDistance0 = 0x2c0;
public const int ClipDistance1 = 0x2c4;
public const int ClipDistance2 = 0x2c8;
public const int ClipDistance3 = 0x2cc;
public const int ClipDistance4 = 0x2d0;
public const int ClipDistance5 = 0x2d4;
public const int ClipDistance6 = 0x2d8;
public const int ClipDistance7 = 0x2dc;
public const int FogCoord = 0x2e8;
public const int TessCoordX = 0x2f0;
public const int TessCoordY = 0x2f4;
public const int InstanceId = 0x2f8;
public const int VertexId = 0x2fc;
public const int TexCoordCount = 10;
public const int TexCoordBase = 0x300;
public const int TexCoordEnd = TexCoordBase + TexCoordCount * 16;
public const int FrontFacing = 0x3fc;
public const int UserAttributesCount = 32;
public const int UserAttributeBase = 0x80;
public const int UserAttributeEnd = UserAttributeBase + UserAttributesCount * 16;
public const int UserAttributeBase = 0x80;
public const int UserAttributeEnd = UserAttributeBase + UserAttributesCount * 16;
public const int UserAttributePerPatchBase = 0x18;
public const int UserAttributePerPatchEnd = 0x200;
public const int LoadOutputMask = 1 << 30;
public const int Mask = 0x3fffffff;
// Note: Those attributes are used internally by the translator
// only, they don't exist on Maxwell.
public const int SpecialMask = 0xf << 24;
public const int FragmentOutputDepth = 0x1000000;
public const int FragmentOutputColorBase = 0x1000010;
public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16;
public const int FragmentOutputIsBgraBase = 0x1000100;
public const int FragmentOutputIsBgraEnd = FragmentOutputIsBgraBase + 8 * 4;
public const int SupportBlockViewInverseX = 0x1000200;
public const int SupportBlockViewInverseY = 0x1000204;
public const int ThreadIdX = 0x2000000;
public const int ThreadIdY = 0x2000004;
public const int ThreadIdZ = 0x2000008;
public const int CtaIdX = 0x2000010;
public const int CtaIdY = 0x2000014;
public const int CtaIdZ = 0x2000018;
public const int LaneId = 0x2000020;
public const int InvocationId = 0x2000024;
public const int PatchVerticesIn = 0x2000028;
public const int EqMask = 0x2000030;
public const int GeMask = 0x2000034;
public const int GtMask = 0x2000038;
public const int LeMask = 0x200003c;
public const int LtMask = 0x2000040;
public const int ThreadKill = 0x2000044;
public const int BaseInstance = 0x2000050;
public const int BaseVertex = 0x2000054;
public const int InstanceIndex = 0x2000058;
public const int VertexIndex = 0x200005c;
public const int DrawIndex = 0x2000060;
public const int UserAttributePerPatchEnd = 0x200;
}
}

View File

@@ -1,210 +0,0 @@
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation
{
readonly struct AttributeInfo
{
private static readonly Dictionary<int, AttributeInfo> _builtInAttributes = new Dictionary<int, AttributeInfo>()
{
{ AttributeConsts.Layer, new AttributeInfo(AttributeConsts.Layer, 0, 1, AggregateType.S32) },
{ AttributeConsts.ViewportIndex, new AttributeInfo(AttributeConsts.ViewportIndex, 0, 1, AggregateType.S32) },
{ AttributeConsts.PointSize, new AttributeInfo(AttributeConsts.PointSize, 0, 1, AggregateType.FP32) },
{ AttributeConsts.PositionX, new AttributeInfo(AttributeConsts.PositionX, 0, 4, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.PositionY, new AttributeInfo(AttributeConsts.PositionX, 1, 4, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.PositionZ, new AttributeInfo(AttributeConsts.PositionX, 2, 4, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.PositionW, new AttributeInfo(AttributeConsts.PositionX, 3, 4, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.ClipDistance0, new AttributeInfo(AttributeConsts.ClipDistance0, 0, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance1, new AttributeInfo(AttributeConsts.ClipDistance0, 1, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance2, new AttributeInfo(AttributeConsts.ClipDistance0, 2, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance3, new AttributeInfo(AttributeConsts.ClipDistance0, 3, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance4, new AttributeInfo(AttributeConsts.ClipDistance0, 4, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance5, new AttributeInfo(AttributeConsts.ClipDistance0, 5, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance6, new AttributeInfo(AttributeConsts.ClipDistance0, 6, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.ClipDistance7, new AttributeInfo(AttributeConsts.ClipDistance0, 7, 8, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.PointCoordX, new AttributeInfo(AttributeConsts.PointCoordX, 0, 2, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.PointCoordY, new AttributeInfo(AttributeConsts.PointCoordX, 1, 2, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.TessCoordX, new AttributeInfo(AttributeConsts.TessCoordX, 0, 3, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.TessCoordY, new AttributeInfo(AttributeConsts.TessCoordX, 1, 3, AggregateType.Vector4 | AggregateType.FP32) },
{ AttributeConsts.InstanceId, new AttributeInfo(AttributeConsts.InstanceId, 0, 1, AggregateType.S32) },
{ AttributeConsts.VertexId, new AttributeInfo(AttributeConsts.VertexId, 0, 1, AggregateType.S32) },
{ AttributeConsts.BaseInstance, new AttributeInfo(AttributeConsts.BaseInstance, 0, 1, AggregateType.S32) },
{ AttributeConsts.BaseVertex, new AttributeInfo(AttributeConsts.BaseVertex, 0, 1, AggregateType.S32) },
{ AttributeConsts.InstanceIndex, new AttributeInfo(AttributeConsts.InstanceIndex, 0, 1, AggregateType.S32) },
{ AttributeConsts.VertexIndex, new AttributeInfo(AttributeConsts.VertexIndex, 0, 1, AggregateType.S32) },
{ AttributeConsts.DrawIndex, new AttributeInfo(AttributeConsts.DrawIndex, 0, 1, AggregateType.S32) },
{ AttributeConsts.FrontFacing, new AttributeInfo(AttributeConsts.FrontFacing, 0, 1, AggregateType.Bool) },
// Special.
{ AttributeConsts.FragmentOutputDepth, new AttributeInfo(AttributeConsts.FragmentOutputDepth, 0, 1, AggregateType.FP32) },
{ AttributeConsts.ThreadKill, new AttributeInfo(AttributeConsts.ThreadKill, 0, 1, AggregateType.Bool) },
{ AttributeConsts.ThreadIdX, new AttributeInfo(AttributeConsts.ThreadIdX, 0, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.ThreadIdY, new AttributeInfo(AttributeConsts.ThreadIdX, 1, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.ThreadIdZ, new AttributeInfo(AttributeConsts.ThreadIdX, 2, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.CtaIdX, new AttributeInfo(AttributeConsts.CtaIdX, 0, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.CtaIdY, new AttributeInfo(AttributeConsts.CtaIdX, 1, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.CtaIdZ, new AttributeInfo(AttributeConsts.CtaIdX, 2, 3, AggregateType.Vector3 | AggregateType.U32) },
{ AttributeConsts.LaneId, new AttributeInfo(AttributeConsts.LaneId, 0, 1, AggregateType.U32) },
{ AttributeConsts.InvocationId, new AttributeInfo(AttributeConsts.InvocationId, 0, 1, AggregateType.S32) },
{ AttributeConsts.PrimitiveId, new AttributeInfo(AttributeConsts.PrimitiveId, 0, 1, AggregateType.S32) },
{ AttributeConsts.PatchVerticesIn, new AttributeInfo(AttributeConsts.PatchVerticesIn, 0, 1, AggregateType.S32) },
{ AttributeConsts.EqMask, new AttributeInfo(AttributeConsts.EqMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
{ AttributeConsts.GeMask, new AttributeInfo(AttributeConsts.GeMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
{ AttributeConsts.GtMask, new AttributeInfo(AttributeConsts.GtMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
{ AttributeConsts.LeMask, new AttributeInfo(AttributeConsts.LeMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
{ AttributeConsts.LtMask, new AttributeInfo(AttributeConsts.LtMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
};
private static readonly Dictionary<int, AttributeInfo> _builtInAttributesPerPatch = new Dictionary<int, AttributeInfo>()
{
{ AttributeConsts.TessLevelOuter0, new AttributeInfo(AttributeConsts.TessLevelOuter0, 0, 4, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.TessLevelOuter1, new AttributeInfo(AttributeConsts.TessLevelOuter0, 1, 4, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.TessLevelOuter2, new AttributeInfo(AttributeConsts.TessLevelOuter0, 2, 4, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.TessLevelOuter3, new AttributeInfo(AttributeConsts.TessLevelOuter0, 3, 4, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.TessLevelInner0, new AttributeInfo(AttributeConsts.TessLevelInner0, 0, 2, AggregateType.Array | AggregateType.FP32) },
{ AttributeConsts.TessLevelInner1, new AttributeInfo(AttributeConsts.TessLevelInner0, 1, 2, AggregateType.Array | AggregateType.FP32) },
};
public int BaseValue { get; }
public int Value { get; }
public int Length { get; }
public AggregateType Type { get; }
public bool IsBuiltin { get; }
public bool IsValid => Type != AggregateType.Invalid;
public AttributeInfo(int baseValue, int index, int length, AggregateType type, bool isBuiltin = true)
{
BaseValue = baseValue;
Value = baseValue + index * 4;
Length = length;
Type = type;
IsBuiltin = isBuiltin;
}
public int GetInnermostIndex()
{
return (Value - BaseValue) / 4;
}
public static bool Validate(ShaderConfig config, int value, bool isOutAttr, bool perPatch)
{
return perPatch ? ValidatePerPatch(config, value, isOutAttr) : Validate(config, value, isOutAttr);
}
public static bool Validate(ShaderConfig config, int value, bool isOutAttr)
{
if (value == AttributeConsts.ViewportIndex && !config.GpuAccessor.QueryHostSupportsViewportIndex())
{
return false;
}
return From(config, value, isOutAttr).IsValid;
}
public static bool ValidatePerPatch(ShaderConfig config, int value, bool isOutAttr)
{
return FromPatch(config, value, isOutAttr).IsValid;
}
public static AttributeInfo From(ShaderConfig config, int value, bool isOutAttr)
{
value &= ~3;
if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd)
{
int location = (value - AttributeConsts.UserAttributeBase) / 16;
AggregateType elemType;
if (config.Stage == ShaderStage.Vertex && !isOutAttr)
{
elemType = config.GpuAccessor.QueryAttributeType(location).ToAggregateType();
}
else
{
elemType = AggregateType.FP32;
}
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | elemType, false);
}
else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
{
int location = (value - AttributeConsts.FragmentOutputColorBase) / 16;
var elemType = config.GpuAccessor.QueryFragmentOutputType(location) switch
{
AttributeType.Sint => AggregateType.S32,
AttributeType.Uint => AggregateType.U32,
_ => AggregateType.FP32
};
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | elemType, false);
}
else if (value == AttributeConsts.SupportBlockViewInverseX || value == AttributeConsts.SupportBlockViewInverseY)
{
return new AttributeInfo(value, 0, 1, AggregateType.FP32);
}
else if (_builtInAttributes.TryGetValue(value, out AttributeInfo info))
{
return info;
}
return new AttributeInfo(value, 0, 0, AggregateType.Invalid);
}
public static AttributeInfo FromPatch(ShaderConfig config, int value, bool isOutAttr)
{
value &= ~3;
if (value >= AttributeConsts.UserAttributePerPatchBase && value < AttributeConsts.UserAttributePerPatchEnd)
{
int offset = (value - AttributeConsts.UserAttributePerPatchBase) & 0xf;
return new AttributeInfo(value - offset, offset >> 2, 4, AggregateType.Vector4 | AggregateType.FP32, false);
}
else if (_builtInAttributesPerPatch.TryGetValue(value, out AttributeInfo info))
{
return info;
}
return new AttributeInfo(value, 0, 0, AggregateType.Invalid);
}
public static bool IsArrayBuiltIn(int attr)
{
if (attr <= AttributeConsts.TessLevelInner1 ||
attr == AttributeConsts.TessCoordX ||
attr == AttributeConsts.TessCoordY)
{
return false;
}
return (attr & AttributeConsts.SpecialMask) == 0;
}
public static bool IsArrayAttributeGlsl(ShaderStage stage, bool isOutAttr)
{
if (isOutAttr)
{
return stage == ShaderStage.TessellationControl;
}
else
{
return stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
}
public static bool IsArrayAttributeSpirv(ShaderStage stage, bool isOutAttr)
{
if (isOutAttr)
{
return false;
}
else
{
return stage == ShaderStage.TessellationControl ||
stage == ShaderStage.TessellationEvaluation ||
stage == ShaderStage.Geometry;
}
}
}
}

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